代码如下:
/**
* Copyright 2011 multibit.org
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.multibit.network;
import com.google.bitcoin.core.*;
import com.google.bitcoin.core.Wallet.SendRequest;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.net.discovery.DnsDiscovery;
import com.google.bitcoin.net.discovery.IrcDiscovery;
import com.google.bitcoin.store.BlockStore;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.store.SPVBlockStore;
import com.google.common.util.concurrent.ListenableFuture;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.multibit.ApplicationDataDirectoryLocator;
import org.multibit.MultiBit;
import org.multibit.controller.Controller;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.file.BackupManager;
import org.multibit.file.FileHandlerException;
import org.multibit.file.WalletSaveException;
import org.multibit.message.Message;
import org.multibit.message.MessageManager;
import org.multibit.model.bitcoin.BitcoinModel;
import org.multibit.model.bitcoin.WalletData;
import org.multibit.model.bitcoin.WalletInfoData;
import org.multibit.model.core.StatusEnum;
import org.multibit.store.MultiBitWalletVersion;
import org.multibit.store.WalletVersionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
*
* MultiBitService encapsulates the interaction with the bitcoin netork
* including: o Peers o Block chain download o sending / receiving bitcoins
*
* The testnet can be slow or flaky as it's a shared resource. You can use the
* testnet in a box
* to do everything purely locally.
*
*/
public class MultiBitService {
private static final String TESTNET3_GENESIS_HASH = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
private static final Logger log = LoggerFactory.getLogger(MultiBitService.class);
public static final String MULTIBIT_PREFIX = "multibit";
public static final String TESTNET_PREFIX = "testnet";
public static final String TESTNET3_PREFIX = "testnet3";
public static final String SEPARATOR = "-";
public static final String BLOCKCHAIN_SUFFIX = ".blockchain";
public static final String SPV_BLOCKCHAIN_SUFFIX = ".spvchain";
public static final String CHECKPOINTS_SUFFIX = ".checkpoints";
public static final String WALLET_SUFFIX = ".wallet";
public static final String IRC_CHANNEL_TEST = "#bitcoinTEST";
public static final String IRC_CHANNEL_TESTNET3 = "#bitcoinTEST3";
public Logger logger = LoggerFactory.getLogger(MultiBitService.class.getName());
private MultiBitPeerGroup peerGroup;
private String blockchainFilename;
private MultiBitBlockChain blockChain;
private BlockStore blockStore;
private final Controller controller;
private final BitcoinController bitcoinController;
private final NetworkParameters networkParameters;
private SecureRandom secureRandom = new SecureRandom();
private MultiBitCheckpointManager checkpointManager;
private String checkpointsFilename;
public static Date genesisBlockCreationDate;
static {
try {
java.text.SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
java.util.Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));
format.setCalendar(cal);
genesisBlockCreationDate = format.parse("2009-01-03 18:15:05");
} catch (ParseException e) {
// Will never happen.
e.printStackTrace();
}
}
/**
* @param bitcoinController BitcoinController
*/
public MultiBitService(BitcoinController bitcoinController) {
this.bitcoinController = bitcoinController;
this.controller = this.bitcoinController;
if (controller == null) {
throw new IllegalStateException("controller cannot be null");
}
if (controller.getModel() == null) {
throw new IllegalStateException("controller.getModel() cannot be null");
}
if (controller.getApplicationDataDirectoryLocator() == null) {
throw new IllegalStateException("controller.getApplicationDataDirectoryLocator() cannot be null");
}
if (this.bitcoinController.getFileHandler() == null) {
throw new IllegalStateException("controller.getFileHandler() cannot be null");
}
networkParameters = this.bitcoinController.getModel().getNetworkParameters();
log.debug("Network parameters = " + networkParameters);
try {
// Load or create the blockStore..
log.debug("Loading/ creating blockstore ...");
blockStore = createBlockStore(null, false);
log.debug("Blockstore is '" + blockStore + "'");
log.debug("Creating blockchain ...");
blockChain = new MultiBitBlockChain(networkParameters, blockStore);
log.debug("Created blockchain '" + blockChain + "' with height " + blockChain.getBestChainHeight());
log.debug("Creating peergroup ...");
createNewPeerGroup();
log.debug("Created peergroup '" + peerGroup + "'");
log.debug("Starting peergroup ...");
peerGroup.start();
log.debug("Started peergroup.");
} catch (BlockStoreException e) {
handleError(e);
} catch (FileHandlerException e) {
handleError(e);
} catch (Exception e) {
handleError(e);
}
FileInputStream stream = null;
try {
stream = new FileInputStream(checkpointsFilename);
checkpointManager = new MultiBitCheckpointManager(networkParameters, stream);
} catch (IOException e) {
log.error("Error creating checkpointManager " + e.getClass().getName() + " " + e.getMessage());
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
log.error("Error tidying up checkpointManager creation" + e.getClass().getName() + " " + e.getMessage());
}
}
}
}
private void handleError(Exception e) {
controller.setOnlineStatus(StatusEnum.ERROR);
MessageManager.INSTANCE.addMessage(new Message(controller.getLocaliser().getString(
"multiBitService.couldNotLoadBlockchain",
new Object[]{blockchainFilename, e.getClass().getName() + " " + e.getMessage()})));
log.error("Error creating MultiBitService " + e.getClass().getName() + " " + e.getMessage());
}
private BlockStore createBlockStore(Date checkpointDate, boolean createNew) throws BlockStoreException, IOException {
BlockStore blockStore = null;
String filePrefix = getFilePrefix();
log.debug("filePrefix = " + filePrefix);
if ("".equals(controller.getApplicationDataDirectoryLocator().getApplicationDataDirectory())) {
blockchainFilename = filePrefix + SPV_BLOCKCHAIN_SUFFIX;
checkpointsFilename = filePrefix + CHECKPOINTS_SUFFIX;
} else {
blockchainFilename = controller.getApplicationDataDirectoryLocator().getApplicationDataDirectory() + File.separator
+ filePrefix + SPV_BLOCKCHAIN_SUFFIX;
checkpointsFilename = controller.getApplicationDataDirectoryLocator().getApplicationDataDirectory() + File.separator
+ filePrefix + CHECKPOINTS_SUFFIX;
}
File blockStoreFile = new File(blockchainFilename);
boolean blockStoreCreatedNew = !blockStoreFile.exists();
// Ensure there is a checkpoints file.
File checkpointsFile = new File(checkpointsFilename);
if (!checkpointsFile.exists()) {
bitcoinController.getFileHandler().copyCheckpointsFromInstallationDirectory(checkpointsFilename);
}
// Use the larger of the installed checkpoints file and the user data checkpoint file (larger = more recent).
ApplicationDataDirectoryLocator applicationDataDirectoryLocator = new ApplicationDataDirectoryLocator();
String installedCheckpointsFilename = applicationDataDirectoryLocator.getInstallationDirectory() + File.separator + MultiBitService.getFilePrefix() + MultiBitService.CHECKPOINTS_SUFFIX;
log.debug("Installed checkpoints file = '" + installedCheckpointsFilename + "'.");
File installedCheckpointsFile = new File(installedCheckpointsFilename);
long sizeOfUserDataCheckpointsFile = 0;
if (checkpointsFile.exists()) {
sizeOfUserDataCheckpointsFile = checkpointsFile.length();
}
if (installedCheckpointsFile.exists() && installedCheckpointsFile.length() > sizeOfUserDataCheckpointsFile) {
// The installed checkpoints file is longer (more checkpoints) so use that.
checkpointsFilename = installedCheckpointsFilename;
checkpointsFile = installedCheckpointsFile;
log.debug("Using installed checkpoints file as it is longer than user data checkpoints - " + installedCheckpointsFile.length() + " bytes versus " + sizeOfUserDataCheckpointsFile + " bytes.");
} else {
log.debug("Using user data checkpoints file as it is longer/same size as installed checkpoints - " + sizeOfUserDataCheckpointsFile + " bytes versus " + installedCheckpointsFile.length() + " bytes.");
}
// If the spvBlockStore is to be created new
// or its size is 0 bytes delete the file so that it is recreated fresh (fix for issue 165).
if (createNew || blockStoreFile.length() == 0) {
// Garbage collect any closed references to the blockchainFile.
System.gc();
blockStoreFile.setWritable(true);
boolean deletedOk = blockStoreFile.delete();
log.debug("Deleting SPV block store '{}' from disk.1", blockchainFilename + ", deletedOk = " + deletedOk);
blockStoreCreatedNew = true;
}
log.debug("Opening / Creating SPV block store '{}' from disk", blockchainFilename);
try {
blockStore = new SPVBlockStore(networkParameters, blockStoreFile);
} catch (BlockStoreException bse) {
try {
log.error("Failed to open/ create SPV block store '{}' from disk", blockchainFilename);
// If the block store creation failed, delete the block store file and try again.
// Garbage collect any closed references to the blockchainFile.
System.gc();
blockStoreFile.setWritable(true);
boolean deletedOk = blockStoreFile.delete();
log.debug("Deleting SPV block store '{}' from disk.2", blockchainFilename + ", deletedOk = " + deletedOk);
blockStoreCreatedNew = true;
blockStore = new SPVBlockStore(networkParameters, blockStoreFile);
} catch (BlockStoreException bse2) {
bse2.printStackTrace();
log.error("Unrecoverable failure in opening block store. This is bad.");
// Throw the exception so that it is indicated on the UI.
throw bse2;
}
}
// Load the existing checkpoint file and checkpoint from today.
if (blockStore != null && checkpointsFile.exists()) {
FileInputStream stream = null;
try {
stream = new FileInputStream(checkpointsFile);
if (checkpointDate == null) {
if (blockStoreCreatedNew) {
// Brand new block store - checkpoint from today. This
// will go back to the last checkpoint.
CheckpointManager.checkpoint(networkParameters, stream, blockStore, (new Date()).getTime() / 1000);
}
} else {
// Use checkpoint date (block replay).
CheckpointManager.checkpoint(networkParameters, stream, blockStore, checkpointDate.getTime() / 1000);
}
} finally {
if (stream != null) {
stream.close();
stream = null;
}
}
}
return blockStore;
}
public void createNewPeerGroup() {
peerGroup = new MultiBitPeerGroup(bitcoinController, networkParameters, blockChain);
peerGroup.setFastCatchupTimeSecs(0); // genesis block
peerGroup.setUserAgent("MultiBit", controller.getLocaliser().getVersionNumber());
boolean peersSpecified = false;
String singleNodeConnection = controller.getModel().getUserPreference(BitcoinModel.SINGLE_NODE_CONNECTION);
String peers = controller.getModel().getUserPreference(BitcoinModel.PEERS);
if (singleNodeConnection != null && !singleNodeConnection.equals("")) {
try {
peerGroup.addAddress(new PeerAddress(InetAddress.getByName(singleNodeConnection.trim())));
peerGroup.setMaxConnections(1);
peersSpecified = true;
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
} else if (peers != null && !peers.equals("")) {
// Split using commas.
String[] peerList = peers.split(",");
if (peerList != null) {
int numberOfPeersAdded = 0;
for (int i = 0; i < peerList.length; i++) {
try {
peerGroup.addAddress(new PeerAddress(InetAddress.getByName(peerList[i].trim())));
numberOfPeersAdded++;
} catch (UnknownHostException e) {
log.error(e.getMessage(), e);
}
}
peerGroup.setMaxConnections(numberOfPeersAdded);
peersSpecified = true;
}
}
if (!peersSpecified) {
// Use DNS for production, IRC for test.
if (TESTNET3_GENESIS_HASH.equals(bitcoinController.getModel().getNetworkParameters().getGenesisBlock().getHashAsString())) {
peerGroup.addPeerDiscovery(new IrcDiscovery(IRC_CHANNEL_TESTNET3));
} else if (NetworkParameters.testNet().equals(bitcoinController.getModel().getNetworkParameters())) {
peerGroup.addPeerDiscovery(new IrcDiscovery(IRC_CHANNEL_TEST));
} else {
peerGroup.addPeerDiscovery(new DnsDiscovery(networkParameters));
}
}
// Add the controller as a PeerEventListener.
peerGroup.addEventListener(bitcoinController.getPeerEventListener());
// Add all existing wallets to the PeerGroup.
if (controller != null && controller.getModel() != null) {
List perWalletDataModels = bitcoinController.getModel().getPerWalletModelDataList();
if (perWalletDataModels != null) {
Iterator iterator = perWalletDataModels.iterator();
if (iterator != null) {
while (iterator.hasNext()) {
WalletData perWalletModelData = iterator.next();
if (perWalletModelData != null && perWalletModelData.getWallet() != null) {
peerGroup.addWallet(perWalletModelData.getWallet());
}
}
}
}
}
}
public void recalculateFastCatchupAndFilter() {
if (peerGroup != null) {
peerGroup.recalculateFastCatchupAndFilter(PeerGroup.FilterRecalculateMode.FORCE_SEND);
}
}
public static String getFilePrefix() {
BitcoinController bitcoinController = MultiBit.getBitcoinController();
// testnet3
if (TESTNET3_GENESIS_HASH.equals(bitcoinController.getModel().getNetworkParameters().getGenesisBlock().getHashAsString())) {
return MULTIBIT_PREFIX + SEPARATOR + TESTNET3_PREFIX;
} else if (NetworkParameters.testNet().equals(bitcoinController.getModel().getNetworkParameters())) {
return MULTIBIT_PREFIX + SEPARATOR + TESTNET_PREFIX;
} else {
return MULTIBIT_PREFIX;
}
}
/**
* Initialize wallet from the wallet filename.
*
* @param walletFilename
* @return perWalletModelData
*/
public WalletData addWalletFromFilename(String walletFilename) throws IOException {
WalletData perWalletModelDataToReturn = null;
Wallet wallet = null;
File walletFile = null;
boolean walletFileIsADirectory = false;
boolean newWalletCreated = false;
if (walletFilename != null) {
walletFile = new File(walletFilename);
if (walletFile.isDirectory()) {
walletFileIsADirectory = true;
} else {
perWalletModelDataToReturn = bitcoinController.getFileHandler().loadFromFile(walletFile);
if (perWalletModelDataToReturn != null) {
wallet = perWalletModelDataToReturn.getWallet();
}
}
}
if (walletFilename == null || walletFilename.equals("") || walletFileIsADirectory) {
// Use default wallet name - create if does not exist.
if ("".equals(controller.getApplicationDataDirectoryLocator().getApplicationDataDirectory())) {
walletFilename = getFilePrefix() + WALLET_SUFFIX;
} else {
walletFilename = controller.getApplicationDataDirectoryLocator().getApplicationDataDirectory() + File.separator
+ getFilePrefix() + WALLET_SUFFIX;
}
walletFile = new File(walletFilename);
if (walletFile.exists()) {
// Wallet file exists with default name.
perWalletModelDataToReturn = bitcoinController.getFileHandler().loadFromFile(walletFile);
if (perWalletModelDataToReturn != null) {
wallet = perWalletModelDataToReturn.getWallet();
newWalletCreated = true;
}
} else {
// Create a brand new wallet - by default unencrypted.
wallet = new Wallet(networkParameters);
ECKey newKey = new ECKey();
wallet.addKey(newKey);
perWalletModelDataToReturn = bitcoinController.getModel().addWallet(bitcoinController, wallet, walletFile.getAbsolutePath());
// Create a wallet info.
WalletInfoData walletInfo = new WalletInfoData(walletFile.getAbsolutePath(), wallet, MultiBitWalletVersion.PROTOBUF);
perWalletModelDataToReturn.setWalletInfo(walletInfo);
// Set a default description.
String defaultDescription = controller.getLocaliser().getString("createNewWalletSubmitAction.defaultDescription");
perWalletModelDataToReturn.setWalletDescription(defaultDescription);
try {
bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelDataToReturn, true);
newWalletCreated = true;
// Backup the wallet and wallet info.
BackupManager.INSTANCE.backupPerWalletModelData(bitcoinController.getFileHandler(), perWalletModelDataToReturn);
} catch (WalletSaveException wse) {
log.error(wse.getClass().getCanonicalName() + " " + wse.getMessage());
MessageManager.INSTANCE.addMessage(new Message(wse.getClass().getCanonicalName() + " " + wse.getMessage()));
} catch (WalletVersionException wve) {
log.error(wve.getClass().getCanonicalName() + " " + wve.getMessage());
MessageManager.INSTANCE.addMessage(new Message(wve.getClass().getCanonicalName() + " " + wve.getMessage()));
}
}
}
if (wallet != null) {
// Add the keys for this wallet to the address book as receiving
// addresses.
List keys = wallet.getKeychain();
if (keys != null) {
if (!newWalletCreated) {
perWalletModelDataToReturn = bitcoinController.getModel().getPerWalletModelDataByWalletFilename(walletFilename);
}
if (perWalletModelDataToReturn != null) {
WalletInfoData walletInfo = perWalletModelDataToReturn.getWalletInfo();
if (walletInfo != null) {
for (ECKey key : keys) {
if (key != null) {
Address address = key.toAddress(networkParameters);
walletInfo.addReceivingAddressOfKey(address);
}
}
}
}
}
// Add wallet to blockchain.
if (blockChain != null) {
blockChain.addWallet(wallet);
} else {
log.error("Could not add wallet '" + walletFilename + "' to the blockChain as the blockChain is missing.\n"
+ "This is bad. MultiBit is currently looking for a blockChain at '" + blockchainFilename + "'");
}
// Add wallet to peergroup.
if (peerGroup != null) {
peerGroup.addWallet(wallet);
peerGroup.addEventListener(bitcoinController.getPeerEventListener());
} else {
log.error("Could not add wallet '" + walletFilename + "' to the peerGroup as the peerGroup is null. This is bad. ");
}
}
return perWalletModelDataToReturn;
}
/**
* Create a new block store.
*
* @param dateToReplayFrom The date to start the replay task from
* @return height tof new block chain after truncate.
* @throws IOException
* @throws BlockStoreException
*/
public int createNewBlockStoreForReplay(Date dateToReplayFrom) throws IOException, BlockStoreException {
log.debug("Loading/ creating blockstore ...");
if (blockStore != null) {
try {
blockStore.close();
blockStore = null;
} catch (NullPointerException npe) {
log.debug("NullPointerException on blockstore close");
}
}
// The CheckpointManager removes a week to cater for block header drift.
// Any date before genesis + 1 week gets adjusted accordingly.
Date genesisPlusOnwWeekAndASecond = new Date(MultiBitService.genesisBlockCreationDate.getTime() + (86400 * 7 + 1) * 1000);
if (dateToReplayFrom != null) {
if (dateToReplayFrom.getTime() < genesisPlusOnwWeekAndASecond.getTime()) {
dateToReplayFrom = genesisPlusOnwWeekAndASecond;
}
blockStore = createBlockStore(dateToReplayFrom, true);
} else {
blockStore = createBlockStore(genesisPlusOnwWeekAndASecond, true);
}
log.debug("Blockstore is '" + blockStore + "'");
log.debug("Creating blockchain ...");
blockChain = new MultiBitBlockChain(bitcoinController.getModel().getNetworkParameters(), blockStore);
log.debug("Created blockchain '" + blockChain + "'");
// Hook up the wallets to the new blockchain.
if (blockChain != null) {
List perWalletModelDataList = bitcoinController.getModel().getPerWalletModelDataList();
for (WalletData loopPerWalletModelData : perWalletModelDataList) {
if (loopPerWalletModelData.getWallet() != null) {
blockChain.addWallet(loopPerWalletModelData.getWallet());
}
}
}
return blockChain.getBestChainHeight();
}
/**
* Send bitcoins from the active wallet.
*
* @return The sent transaction (may be null if there were insufficient
* funds for send)
* @throws KeyCrypterException
* @throws IOException
* @throws AddressFormatException
*/
public Transaction sendCoins(WalletData perWalletModelData, SendRequest sendRequest,
CharSequence password) throws java.io.IOException, AddressFormatException, KeyCrypterException {
// Ping the peers to check the bitcoin network connection
List connectedPeers = peerGroup.getConnectedPeers();
boolean atLeastOnePingWorked = false;
if (connectedPeers != null) {
for (Peer peer : connectedPeers) {
log.debug("Ping: {}", peer.getAddress().toString());
try {
ListenableFuture result = peer.ping();
result.get(4, TimeUnit.SECONDS);
atLeastOnePingWorked = true;
break;
} catch (ProtocolException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
} catch (InterruptedException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
} catch (ExecutionException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
} catch (TimeoutException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
}
}
}
if (!atLeastOnePingWorked) {
throw new IllegalStateException("All peers failed ping test (check network)");
}
// Send the coins
log.debug("MultiBitService#sendCoins - Just about to send coins");
KeyParameter aesKey = null;
if (perWalletModelData.getWallet().getEncryptionType() != EncryptionType.UNENCRYPTED) {
aesKey = perWalletModelData.getWallet().getKeyCrypter().deriveKey(password);
}
sendRequest.aesKey = aesKey;
sendRequest.fee = BigInteger.ZERO;
sendRequest.feePerKb = BitcoinModel.SEND_FEE_PER_KB_DEFAULT;
sendRequest.tx.getConfidence().addEventListener(perWalletModelData.getWallet().getTxConfidenceListener());
try {
// The transaction is already added to the wallet (in SendBitcoinConfirmAction) so here we just need
// to sign it, commit it and broadcast it.
perWalletModelData.getWallet().sign(sendRequest);//签名
perWalletModelData.getWallet().commitTx(sendRequest.tx);//提交
// The tx has been committed to the pending pool by this point (via sendCoinsOffline -> commitTx), so it has
// a txConfidenceListener registered. Once the tx is broadcast the peers will update the memory pool with the
// count of seen peers, the memory pool will update the transaction confidence object, that will invoke the
// txConfidenceListener which will in turn invoke the wallets event listener onTransactionConfidenceChanged
// method.
peerGroup.broadcastTransaction(sendRequest.tx);//广播
log.debug("Sending transaction '" + Utils.bytesToHexString(sendRequest.tx.bitcoinSerialize()) + "'");
} catch (VerificationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Transaction sendTransaction = sendRequest.tx;
log.debug("MultiBitService#sendCoins - Sent coins has completed");
assert sendTransaction != null;
// We should never try to send more coins than we have!
// throw an exception if sendTransaction is null - no money.
if (sendTransaction != null) {
log.debug("MultiBitService#sendCoins - Sent coins. Transaction hash is {}", sendTransaction.getHashAsString() + ", identityHashcode = " + System.identityHashCode(sendTransaction));
if (sendTransaction.getConfidence() != null) {
log.debug("Added bitcoinController " + System.identityHashCode(bitcoinController) + " as listener to tx = " + sendTransaction.getHashAsString());
sendTransaction.getConfidence().addEventListener(bitcoinController);
} else {
log.debug("Cannot add bitcoinController as listener to tx = " + sendTransaction.getHashAsString() + " no transactionConfidence");
}
try {
bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, false);
} catch (WalletSaveException wse) {
log.error(wse.getClass().getCanonicalName() + " " + wse.getMessage());
MessageManager.INSTANCE.addMessage(new Message(wse.getClass().getCanonicalName() + " " + wse.getMessage()));
} catch (WalletVersionException wse) {
log.error(wse.getClass().getCanonicalName() + " " + wse.getMessage());
MessageManager.INSTANCE.addMessage(new Message(wse.getClass().getCanonicalName() + " " + wse.getMessage()));
}
try {
// Notify other wallets of the send (it might be a send to or from them).
List perWalletModelDataList = bitcoinController.getModel().getPerWalletModelDataList();
if (perWalletModelDataList != null) {
for (WalletData loopPerWalletModelData : perWalletModelDataList) {
if (!perWalletModelData.getWalletFilename().equals(loopPerWalletModelData.getWalletFilename())) {
Wallet loopWallet = loopPerWalletModelData.getWallet();
if (loopWallet.isPendingTransactionRelevant(sendTransaction)) {
// The loopPerWalletModelData is marked as dirty.
if (loopPerWalletModelData.getWalletInfo() != null) {
synchronized (loopPerWalletModelData.getWalletInfo()) {
loopPerWalletModelData.setDirty(true);
}
} else {
loopPerWalletModelData.setDirty(true);
}
if (loopWallet.getTransaction(sendTransaction.getHash()) == null) {
log.debug("MultiBit adding a new pending transaction for the wallet '"
+ loopPerWalletModelData.getWalletDescription() + "'\n" + sendTransaction.toString());
loopWallet.receivePending(sendTransaction, null);
}
}
}
}
}
} catch (ScriptException e) {
e.printStackTrace();
} catch (VerificationException e) {
e.printStackTrace();
}
}
return sendTransaction;
}
public PeerGroup getPeerGroup() {
return peerGroup;
}
public MultiBitBlockChain getChain() {
return blockChain;
}
public BlockStore getBlockStore() {
return blockStore;
}
public SecureRandom getSecureRandom() {
return secureRandom;
}
;
public String getCheckpointsFilename() {
return checkpointsFilename;
}
public MultiBitCheckpointManager getCheckpointManager() {
return checkpointManager;
}
}
2.国际化部分发送按钮:
/**package org.multibit.viewsystem.swing.action;
* Complete the transaction to work out the fee) and then show the send bitcoin confirm dialog.
*/
@Override
public void actionPerformed(ActionEvent e) {
if (abort()) {
return;
}
SendBitcoinConfirmDialog sendBitcoinConfirmDialog = null;
ValidationErrorDialog validationErrorDialog = null;
try {
String sendAddress = dataProvider.getAddress();//获取输入的地址
String sendAmount = dataProvider.getAmount();//获取输入的金额
Validator validator = new Validator(super.bitcoinController);
if (validator.validate(sendAddress, sendAmount)) {
// The address and amount are valid.
//能够进入if代码块内证明validator.validate(sendAddress, sendAmount)返回的是true
//说明接受比特币的地址和所输入的金额是合法的
// Create a SendRequest.
Address sendAddressObject;
sendAddressObject = new Address(bitcoinController.getModel().getNetworkParameters(), sendAddress);
//创建对象时传的参数bitcoinController.getModel().getNetworkParameters()是比特币网络类型,可以是测试网络,可以是正式生产网络
SendRequest sendRequest = SendRequest.to(sendAddressObject, Utils.toNanoCoins(sendAmount));
sendRequest.ensureMinRequiredFee = true;
sendRequest.fee = BigInteger.ZERO;
sendRequest.feePerKb = BitcoinModel.SEND_FEE_PER_KB_DEFAULT;
// Note - Request is populated with the AES key in the SendBitcoinNowAction after the user has entered it on the SendBitcoinConfirm form.
// Complete it (which works out the fee) but do not sign it yet.
log.debug("Just about to complete the tx (and calculate the fee)...");
boolean completedOk;
try {
//完成一个空白交易,即除了用私钥进行签名,其他工作都已经准备完毕
bitcoinController.getModel().getActiveWallet().completeTx(sendRequest, false);
completedOk = true;
log.debug("The fee after completing the transaction was " + sendRequest.fee);
} catch (InsufficientMoneyException ime) {
completedOk = false;
}
if (completedOk) {
// There is enough money.
sendBitcoinConfirmDialog = new SendBitcoinConfirmDialog(super.bitcoinController, mainFrame, sendRequest);
sendBitcoinConfirmDialog.setVisible(true);
} else {
// There is not enough money.
// TODO setup validation parameters accordingly so that it displays ok.
validationErrorDialog = new ValidationErrorDialog(super.bitcoinController, mainFrame, sendRequest, true);
validationErrorDialog.setVisible(true);
}
} else {
validationErrorDialog = new ValidationErrorDialog(super.bitcoinController, mainFrame, null, false);
validationErrorDialog.setVisible(true);
}
} catch (WrongNetworkException e1) {
logMessage(e1);
} catch (AddressFormatException e1) {
logMessage(e1);
} catch (KeyCrypterException e1) {
logMessage(e1);
} catch (Exception e1) {
logMessage(e1);
}
}
代码如下:
/**
* Validate a String address and amount.
*
* @param address
* @param amount
* @return
*/
public boolean validate(String address, String amount) {
clearValidationState();
boolean validAddress = validateAddress(address);
boolean validAmount = validateAmount(amount);
return validAddress && validAmount;
}
private boolean validateAmount(String amount) {
// Copy amount to wallet preferences.
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_AMOUNT_VALUE, amount);
Boolean amountValidatesOk = Boolean.TRUE;
Boolean amountIsInvalid = Boolean.FALSE;
Boolean notEnoughFunds = Boolean.FALSE;
Boolean amountIsMissing = Boolean.FALSE;
Boolean amountIsNegativeOrZero = Boolean.FALSE;
Boolean amountIsTooSmall = Boolean.FALSE;
// See if the amount is missing.
if (amount == null || "".equals(amount) || amount.trim().length() == 0) {
amountIsMissing = Boolean.TRUE;
amountValidatesOk = Boolean.FALSE;
} else {
// See if the amount is a number.
BigInteger amountBigInteger = null;
try {
CurrencyConverterResult converterResult = CurrencyConverter.INSTANCE.parseToBTCNotLocalised(amount);
if (converterResult.isBtcMoneyValid()) {
// Parses ok.
amountBigInteger = converterResult.getBtcMoney().getAmount().toBigInteger();
} else {
amountIsInvalid = Boolean.TRUE;
amountValidatesOk = Boolean.FALSE;
}
} catch (NumberFormatException nfe) {
amountValidatesOk = Boolean.FALSE;
amountIsInvalid = Boolean.TRUE;
} catch (ArithmeticException ae) {
amountValidatesOk = Boolean.FALSE;
amountIsInvalid = Boolean.TRUE;
}
// See if the amount is negative or zero.
if (amountValidatesOk.booleanValue()) {
if (amountBigInteger.compareTo(BigInteger.ZERO) <= 0) {
amountValidatesOk = Boolean.FALSE;
amountIsNegativeOrZero = Boolean.TRUE;
} else {
if (amountBigInteger.compareTo(Transaction.MIN_NONDUST_OUTPUT) < 0) {
amountValidatesOk = Boolean.FALSE;
amountIsTooSmall = Boolean.TRUE;
} else {
// The fee is worked out in detail later, but we know it will be at least the minimum reference amount.
BigInteger totalSpend = amountBigInteger.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
BigInteger availableBalance = this.bitcoinController.getModel().getActiveWallet().getBalance(BalanceType.AVAILABLE);
BigInteger estimatedBalance = this.bitcoinController.getModel().getActiveWallet().getBalance(BalanceType.ESTIMATED);
log.debug("Amount = " + amountBigInteger.toString() + ", fee of at least " + Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.toString()
+ ", totalSpend = " + totalSpend.toString() + ", availableBalance = " + availableBalance.toString() + ", estimatedBalance = " + estimatedBalance.toString());
if (totalSpend.compareTo(availableBalance) > 0) {
// Not enough funds.
amountValidatesOk = Boolean.FALSE;
notEnoughFunds = Boolean.TRUE;
}
}
}
}
}
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_AMOUNT_IS_MISSING, amountIsMissing.toString());
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_AMOUNT_IS_NEGATIVE_OR_ZERO,
amountIsNegativeOrZero.toString());
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_AMOUNT_IS_TOO_SMALL, amountIsTooSmall.toString());
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_AMOUNT_IS_INVALID, amountIsInvalid.toString());
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_NOT_ENOUGH_FUNDS, notEnoughFunds.toString());
return amountValidatesOk.booleanValue();
}
private boolean validateAddress(String address) {
Boolean addressIsInvalid = Boolean.TRUE;
if (address != null && !address.isEmpty()) {
// Copy address to wallet preferences.
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_ADDRESS_VALUE, address);
try {
new Address(this.bitcoinController.getModel().getNetworkParameters(), address);
addressIsInvalid = Boolean.FALSE;
} catch (AddressFormatException afe) {
// Carry on.
} catch (java.lang.StringIndexOutOfBoundsException e) {
// Carry on.
}
} else {
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_ADDRESS_VALUE, "");
}
this.bitcoinController.getModel().setActiveWalletPreference(BitcoinModel.VALIDATION_ADDRESS_IS_INVALID, addressIsInvalid.toString());
return !addressIsInvalid.booleanValue();
}
如果地址和金额输入是合法的,并且空白交易已经生成成功
completedOk = true;
则会调用bitcoin确认对话框
代码如下:
package org.multibit.viewsystem.swing.view.dialogs;
import org.multibit.viewsystem.swing.view.panels.SendBitcoinConfirmPanel;
import java.awt.BorderLayout;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.FontMetrics;
import javax.swing.ImageIcon;
import org.multibit.controller.Controller;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.utils.ImageLoader;
import org.multibit.viewsystem.swing.MultiBitFrame;
import org.multibit.viewsystem.swing.view.components.FontSizer;
import org.multibit.viewsystem.swing.view.components.MultiBitDialog;
import com.google.bitcoin.core.Wallet.SendRequest;
/**
* The send bitcoin confirm dialog.
*/
public class SendBitcoinConfirmDialog extends MultiBitDialog {
private static final long serialVersionUID = 191435612345057705L;
private static final int HEIGHT_DELTA = 150;
private static final int WIDTH_DELTA = 400;
private MultiBitFrame mainFrame;
private SendBitcoinConfirmPanel sendBitcoinConfirmPanel;
private final Controller controller;
private final BitcoinController bitcoinController;
private final SendRequest sendRequest;
/**
* Creates a new {@link SendBitcoinConfirmDialog}.
*/
public SendBitcoinConfirmDialog(BitcoinController bitcoinController, MultiBitFrame mainFrame, SendRequest sendRequest) {
super(mainFrame, bitcoinController.getLocaliser().getString("sendBitcoinConfirmView.title"));
this.bitcoinController = bitcoinController;
this.controller = this.bitcoinController;
this.mainFrame = mainFrame;
this.sendRequest = sendRequest;
ImageIcon imageIcon = ImageLoader.createImageIcon(ImageLoader.MULTIBIT_ICON_FILE);
if (imageIcon != null) {
setIconImage(imageIcon.getImage());
}
initUI();
sendBitcoinConfirmPanel.getCancelButton().requestFocusInWindow();
applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale()));
}
/**
* Initialise bitcoin confirm dialog.
*/
public void initUI() {
FontMetrics fontMetrics = getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont());
if (mainFrame != null) {
int minimumHeight = fontMetrics.getHeight() * 11 + HEIGHT_DELTA;
int minimumWidth = Math.max(fontMetrics.stringWidth(MultiBitFrame.EXAMPLE_LONG_FIELD_TEXT), fontMetrics.stringWidth(controller.getLocaliser().getString("sendBitcoinConfirmView.message"))) + WIDTH_DELTA;
setMinimumSize(new Dimension(minimumWidth, minimumHeight));
positionDialogRelativeToParent(this, 0.5D, 0.47D);
}
sendBitcoinConfirmPanel = new SendBitcoinConfirmPanel(this.bitcoinController, mainFrame, this, sendRequest);
sendBitcoinConfirmPanel.setOpaque(false);
setLayout(new BorderLayout());
add(sendBitcoinConfirmPanel, BorderLayout.CENTER);
}
}
生成的方法中又会创建一个SendBitcoinConfirmPanel类的对象
代码如下:
package org.multibit.viewsystem.swing.view.panels;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.Utils;
import com.google.bitcoin.core.Wallet.SendRequest;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.multibit.MultiBit;
import org.multibit.controller.Controller;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.exchange.CurrencyConverter;
import org.multibit.model.bitcoin.BitcoinModel;
import org.multibit.model.bitcoin.WalletBusyListener;
import org.multibit.utils.ImageLoader;
import org.multibit.viewsystem.swing.ColorAndFontConstants;
import org.multibit.viewsystem.swing.MultiBitFrame;
import org.multibit.viewsystem.swing.action.CancelBackToParentAction;
import org.multibit.viewsystem.swing.action.OkBackToParentAction;
import org.multibit.viewsystem.swing.action.SendBitcoinNowAction;
import org.multibit.viewsystem.swing.view.components.MultiBitButton;
import org.multibit.viewsystem.swing.view.components.MultiBitDialog;
import org.multibit.viewsystem.swing.view.components.MultiBitLabel;
import org.multibit.viewsystem.swing.view.components.MultiBitTitledPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
/**
* The send bitcoin confirm panel.
*/
public class SendBitcoinConfirmPanel extends JPanel implements WalletBusyListener {
private static final long serialVersionUID = 191435612399957705L;
private static final Logger log = LoggerFactory.getLogger(SendBitcoinConfirmPanel.class);
private static final int STENT_WIDTH = 10;
private MultiBitFrame mainFrame;
private MultiBitDialog sendBitcoinConfirmDialog;
private final Controller controller;
private final BitcoinController bitcoinController;
private MultiBitLabel sendAddressText;
private MultiBitLabel sendLabelText;
private MultiBitLabel sendAmountText;
private MultiBitLabel sendFeeText;
private String sendAddress;
private String sendLabel;
private SendRequest sendRequest;
private MultiBitLabel confirmText1;
private MultiBitLabel confirmText2;
private SendBitcoinNowAction sendBitcoinNowAction;
private MultiBitButton sendButton;
private MultiBitButton cancelButton;
private JPasswordField walletPasswordField;
private MultiBitLabel walletPasswordPromptLabel;
private MultiBitLabel explainLabel;
private static SendBitcoinConfirmPanel thisPanel = null;
private static ImageIcon shapeTriangleIcon;
private static ImageIcon shapeSquareIcon;
private static ImageIcon shapeHeptagonIcon;
private static ImageIcon shapeHexagonIcon;
private static ImageIcon progress0Icon;
static {
shapeTriangleIcon = ImageLoader.createImageIcon(ImageLoader.SHAPE_TRIANGLE_ICON_FILE);
shapeSquareIcon = ImageLoader.createImageIcon(ImageLoader.SHAPE_SQUARE_ICON_FILE);
shapeHeptagonIcon = ImageLoader.createImageIcon(ImageLoader.SHAPE_PENTAGON_ICON_FILE);
shapeHexagonIcon = ImageLoader.createImageIcon(ImageLoader.SHAPE_HEXAGON_ICON_FILE);
progress0Icon = ImageLoader.createImageIcon(ShowTransactionsPanel.PROGRESS_0_ICON_FILE);
}
/**
* Creates a new {@link SendBitcoinConfirmPanel}.
*/
public SendBitcoinConfirmPanel(BitcoinController bitcoinController, MultiBitFrame mainFrame, MultiBitDialog sendBitcoinConfirmDialog, SendRequest sendRequest) {
super();
this.bitcoinController = bitcoinController;
this.controller = this.bitcoinController;
this.mainFrame = mainFrame;
this.sendBitcoinConfirmDialog = sendBitcoinConfirmDialog;
this.sendRequest = sendRequest;
thisPanel = this;
initUI();
cancelButton.requestFocusInWindow();
applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale()));
this.bitcoinController.registerWalletBusyListener(this);
}
/**
* Initialise bitcoin confirm panel.
*/
public void initUI() {
JPanel mainPanel = new JPanel();
mainPanel.setOpaque(false);
setLayout(new BorderLayout());
add(mainPanel, BorderLayout.CENTER);
mainPanel.setLayout(new GridBagLayout());
String[] keys = new String[] { "sendBitcoinPanel.addressLabel",
"sendBitcoinPanel.labelLabel", "sendBitcoinPanel.amountLabel",
"showPreferencesPanel.feeLabel.text", "showExportPrivateKeysPanel.walletPasswordPrompt"};
int stentWidth = MultiBitTitledPanel.calculateStentWidthForKeys(controller.getLocaliser(), keys, mainPanel)
+ ExportPrivateKeysPanel.STENT_DELTA;
// Get the data out of the wallet preferences.
sendAddress = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_ADDRESS);
sendLabel = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_LABEL);
String sendAmount = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_AMOUNT) + " " + controller.getLocaliser(). getString("sendBitcoinPanel.amountUnitLabel");
String sendAmountLocalised = CurrencyConverter.INSTANCE.prettyPrint(sendAmount);
String fee = "0";
if (sendRequest != null) {
fee = Utils.bitcoinValueToPlainString(sendRequest.fee);
}
String sendFeeLocalised = CurrencyConverter.INSTANCE.prettyPrint(fee);
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 0;
constraints.gridy = 0;
constraints.weightx = 0.3;
constraints.weighty = 0.1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(MultiBitTitledPanel.createStent(STENT_WIDTH), constraints);
ImageIcon bigIcon = ImageLoader.createImageIcon(ImageLoader.MULTIBIT_128_ICON_FILE);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 1;
constraints.gridy = 2;
constraints.weightx = 0.5;
constraints.weighty = 0.2;
constraints.gridwidth = 1;
constraints.gridheight = 5;
constraints.anchor = GridBagConstraints.CENTER;
JLabel bigIconLabel = new JLabel(bigIcon);
mainPanel.add(bigIconLabel, constraints);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 2;
constraints.gridy = 0;
constraints.weightx = 0.3;
constraints.weighty = 0.1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(MultiBitTitledPanel.createStent(STENT_WIDTH, STENT_WIDTH), constraints);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 7;
constraints.gridy = 1;
constraints.weightx = 0.3;
constraints.weighty = 0.1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(MultiBitTitledPanel.createStent(STENT_WIDTH), constraints);
explainLabel = new MultiBitLabel("");
explainLabel.setText(controller.getLocaliser().getString("sendBitcoinConfirmView.message"));
constraints.fill = GridBagConstraints.NONE;
constraints.gridx = 3;
constraints.gridy = 1;
constraints.weightx = 0.8;
constraints.weighty = 0.4;
constraints.gridwidth = 5;
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(explainLabel, constraints);
mainPanel.add(MultiBitTitledPanel.createStent(explainLabel.getPreferredSize().width, explainLabel.getPreferredSize().height), constraints);
JPanel detailPanel = new JPanel(new GridBagLayout());
detailPanel.setBackground(ColorAndFontConstants.VERY_LIGHT_BACKGROUND_COLOR);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 3;
constraints.gridy = 2;
constraints.weightx = 0.6;
constraints.weighty = 0.8;
constraints.gridwidth = 3;
constraints.gridheight = 5;
constraints.anchor = GridBagConstraints.CENTER;
mainPanel.add(detailPanel, constraints);
GridBagConstraints constraints2 = new GridBagConstraints();
constraints2.fill = GridBagConstraints.HORIZONTAL;
constraints2.gridx = 0;
constraints2.gridy = 0;
constraints2.weightx = 0.3;
constraints2.weighty = 0.05;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_START;
detailPanel.add(MultiBitTitledPanel.createStent(stentWidth), constraints2);
constraints2.fill = GridBagConstraints.HORIZONTAL;
constraints2.gridx = 1;
constraints2.gridy = 0;
constraints2.weightx = 0.05;
constraints2.weighty = 0.05;
constraints2.gridwidth = 1;
constraints2.gridheight = 1;
constraints2.anchor = GridBagConstraints.CENTER;
detailPanel.add(MultiBitTitledPanel.createStent(MultiBitTitledPanel.SEPARATION_BETWEEN_NAME_VALUE_PAIRS),
constraints2);
JLabel forcer1 = new JLabel();
forcer1.setOpaque(false);
constraints2.fill = GridBagConstraints.HORIZONTAL;
constraints2.gridx = 2;
constraints2.gridy = 0;
constraints2.weightx = 10;
constraints2.weighty = 0.05;
constraints2.gridwidth = 1;
constraints2.gridheight = 1;
constraints2.anchor = GridBagConstraints.LINE_END;
detailPanel.add(forcer1, constraints2);
MultiBitLabel sendAddressLabel = new MultiBitLabel("");
sendAddressLabel.setText(controller.getLocaliser().getString("sendBitcoinPanel.addressLabel"));
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 0;
constraints2.gridy = 1;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_END;
detailPanel.add(sendAddressLabel, constraints2);
sendAddressText = new MultiBitLabel("");
sendAddressText.setText(sendAddress);
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 2;
constraints2.gridy = 1;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_START;
detailPanel.add(sendAddressText, constraints2);
MultiBitLabel sendLabelLabel = new MultiBitLabel("");
sendLabelLabel.setText(controller.getLocaliser().getString("sendBitcoinPanel.labelLabel"));
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 0;
constraints2.gridy = 2;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_END;
detailPanel.add(sendLabelLabel, constraints2);
sendLabelText = new MultiBitLabel("");
sendLabelText.setText(sendLabel);
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 2;
constraints2.gridy = 2;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_START;
detailPanel.add(sendLabelText, constraints2);
MultiBitLabel sendAmountLabel = new MultiBitLabel("");
sendAmountLabel.setText(controller.getLocaliser().getString("sendBitcoinPanel.amountLabel"));
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 0;
constraints2.gridy = 3;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_END;
detailPanel.add(sendAmountLabel, constraints2);
sendAmountText = new MultiBitLabel("");
sendAmountText.setText(sendAmountLocalised);
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 2;
constraints2.gridy = 3;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_START;
detailPanel.add(sendAmountText, constraints2);
MultiBitLabel sendFeeLabel = new MultiBitLabel("");
sendFeeLabel.setText(controller.getLocaliser().getString("showPreferencesPanel.feeLabel.text"));
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 0;
constraints2.gridy = 4;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_END;
detailPanel.add(sendFeeLabel, constraints2);
sendFeeText = new MultiBitLabel("");
sendFeeText.setText(sendFeeLocalised);
constraints2.fill = GridBagConstraints.NONE;
constraints2.gridx = 2;
constraints2.gridy = 4;
constraints2.weightx = 0.3;
constraints2.weighty = 0.1;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_START;
detailPanel.add(sendFeeText, constraints2);
constraints2.fill = GridBagConstraints.HORIZONTAL;
constraints2.gridx = 0;
constraints2.gridy = 5;
constraints2.weightx = 0.3;
constraints2.weighty = 0.05;
constraints2.gridwidth = 1;
constraints2.anchor = GridBagConstraints.LINE_START;
detailPanel.add(MultiBitTitledPanel.createStent(stentWidth), constraints2);
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 3;
constraints.gridy = 7;
constraints.weightx = 0.3;
constraints.weighty = 0.3;
constraints.gridheight = 1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(MultiBitTitledPanel.createStent(stentWidth), constraints);
// Add wallet password field.
walletPasswordPromptLabel = new MultiBitLabel(controller.getLocaliser().getString("showExportPrivateKeysPanel.walletPasswordPrompt"));
constraints.fill = GridBagConstraints.NONE;
constraints.gridx = 3;
constraints.gridy = 8;
constraints.weightx = 0.3;
constraints.weighty = 0.1;
constraints.gridheight = 1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_END;
mainPanel.add(walletPasswordPromptLabel, constraints);
mainPanel.add(MultiBitTitledPanel.createStent(walletPasswordPromptLabel.getPreferredSize().width, walletPasswordPromptLabel.getPreferredSize().height), constraints);
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 4;
constraints.gridy = 7;
constraints.weightx = 0.05;
constraints.weighty = 0.1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.CENTER;
mainPanel.add(MultiBitTitledPanel.createStent(MultiBitTitledPanel.SEPARATION_BETWEEN_NAME_VALUE_PAIRS),
constraints);
JLabel forcer2 = new JLabel();
forcer2.setOpaque(false);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 5;
constraints.gridy = 7;
constraints.weightx = 10;
constraints.weighty = 0.05;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.LINE_END;
mainPanel.add(forcer2, constraints);
JPanel filler4 = new JPanel();
filler4.setOpaque(false);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 3;
constraints.gridy = 7;
constraints.weightx = 0.3;
constraints.weighty = 0.01;
constraints.gridheight = 1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(filler4, constraints);
walletPasswordField = new JPasswordField(24);
walletPasswordField.setMinimumSize(new Dimension(200, 20));
constraints.fill = GridBagConstraints.NONE;
constraints.gridx = 5;
constraints.gridy = 8;
constraints.weightx = 0.3;
constraints.weighty = 0.1;
constraints.gridheight = 1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(walletPasswordField, constraints);
mainPanel.add(MultiBitTitledPanel.createStent(200, 20), constraints);
JPanel filler5 = new JPanel();
filler4.setOpaque(false);
constraints.fill = GridBagConstraints.BOTH;
constraints.gridx = 3;
constraints.gridy = 9;
constraints.weightx = 0.3;
constraints.weighty = 0.01;
constraints.gridheight = 1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(filler5, constraints);
if (this.bitcoinController.getModel().getActiveWallet() != null) {
if (this.bitcoinController.getModel().getActiveWallet().getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES) {
// Need wallet password.
walletPasswordField.setEnabled(true);
walletPasswordPromptLabel.setEnabled(true);
} else {
// No wallet password required.
walletPasswordField.setEnabled(false);
walletPasswordPromptLabel.setEnabled(false);
}
}
JPanel buttonPanel = new JPanel();
buttonPanel.setOpaque(false);
//buttonPanel.setBorder(BorderFactory.createLineBorder(Color.RED));
constraints.fill = GridBagConstraints.NONE;
constraints.gridx = 3;
constraints.gridy = 10;
constraints.weightx = 0.8;
constraints.weighty = 0.1;
constraints.gridwidth = 4;
constraints.gridheight = 1;
constraints.anchor = GridBagConstraints.LINE_END;
mainPanel.add(buttonPanel, constraints);
CancelBackToParentAction cancelAction = new CancelBackToParentAction(controller, ImageLoader.createImageIcon(ImageLoader.CROSS_ICON_FILE), sendBitcoinConfirmDialog);
cancelButton = new MultiBitButton(cancelAction, controller);
buttonPanel.add(cancelButton);
sendBitcoinNowAction = new SendBitcoinNowAction(mainFrame, this.bitcoinController, this, walletPasswordField, ImageLoader.createImageIcon(ImageLoader.SEND_BITCOIN_ICON_FILE), sendRequest);
sendButton = new MultiBitButton(sendBitcoinNowAction, controller);
buttonPanel.add(sendButton);
confirmText1 = new MultiBitLabel("");
confirmText1.setText(" ");
constraints.fill = GridBagConstraints.NONE;
constraints.gridx = 1;
constraints.gridy = 11;
constraints.weightx = 0.8;
constraints.weighty = 0.15;
constraints.gridwidth = 6;
constraints.anchor = GridBagConstraints.LINE_END;
mainPanel.add(confirmText1, constraints);
JLabel filler3 = new JLabel();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 7;
constraints.gridy = 11;
constraints.weightx = 0.05;
constraints.weighty = 0.1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(filler3, constraints);
confirmText2 = new MultiBitLabel(" ");
constraints.fill = GridBagConstraints.NONE;
constraints.gridx = 1;
constraints.gridy = 12;
constraints.weightx = 0.8;
constraints.weighty = 0.15;
constraints.gridwidth = 6;
constraints.anchor = GridBagConstraints.LINE_END;
mainPanel.add(confirmText2, constraints);
JLabel filler6 = new JLabel();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.gridx = 7;
constraints.gridy = 12;
constraints.weightx = 0.05;
constraints.weighty = 0.1;
constraints.gridwidth = 1;
constraints.anchor = GridBagConstraints.LINE_START;
mainPanel.add(filler6, constraints);
enableSendAccordingToNumberOfConnectedPeersAndWalletBusy();
}
private void enableSendAccordingToNumberOfConnectedPeersAndWalletBusy() {
boolean enableSend = false;
String message = " ";
if (this.controller.getModel() != null) {
String singleNodeConnection = this.controller.getModel().getUserPreference(BitcoinModel.SINGLE_NODE_CONNECTION);
boolean singleNodeConnectionOverride = singleNodeConnection != null && singleNodeConnection.trim().length() > 0;
String peers = this.controller.getModel().getUserPreference(BitcoinModel.PEERS);
boolean singlePeerOverride = peers != null && peers.split(",").length == 1;
if (thisPanel.sendBitcoinNowAction != null) {
if (!singleNodeConnectionOverride && !singlePeerOverride && this.bitcoinController.getModel().getNumberOfConnectedPeers() < BitcoinModel.MINIMUM_NUMBER_OF_CONNECTED_PEERS_BEFORE_SEND_IS_ENABLED) {
// Disable send button
enableSend = false;
message = controller.getLocaliser().getString("sendBitcoinConfirmView.multibitMustBeOnline");
} else {
// Enable send button
enableSend = true;
message = " ";
}
if (this.bitcoinController.getModel().getActivePerWalletModelData().isBusy()) {
enableSend = false;
message = controller.getLocaliser().getString("multiBitSubmitAction.walletIsBusy",
new Object[]{controller.getLocaliser().getString(this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())});
}
thisPanel.sendBitcoinNowAction.setEnabled(enableSend);
}
}
if (sendBitcoinNowAction != null) {
sendBitcoinNowAction.setEnabled(enableSend);
if (confirmText1 != null) {
if (enableSend) {
// Only clear the 'multibitMustBeOnline' message.
if (controller.getLocaliser().getString("sendBitcoinConfirmView.multibitMustBeOnline").equals(confirmText1.getText())) {
confirmText1.setText(message);
}
} else {
confirmText1.setText(message);
}
}
}
}
public void setMessageText(final String message1) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
confirmText1.setText(message1);
}});
invalidate();
validate();
repaint();
}
public void setMessageText(final String message1, final String message2) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
confirmText1.setText(message1);
confirmText2.setText(" " + message2);
}});
invalidate();
validate();
repaint();
}
public void clearAfterSend() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
walletPasswordField.setText("");
walletPasswordField.setVisible(false);
explainLabel.setVisible(false);
walletPasswordPromptLabel.setVisible(false);
}});
}
public void showOkButton() {
OkBackToParentAction okAction = new OkBackToParentAction(controller, sendBitcoinConfirmDialog);
sendButton.setAction(okAction);
cancelButton.setVisible(false);
}
public static void updatePanel() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (thisPanel != null && thisPanel.isVisible()) {
final BitcoinController bitcoinController = MultiBit.getBitcoinController();
if (bitcoinController != null) {
String singleNodeConnection = bitcoinController.getModel().getUserPreference(BitcoinModel.SINGLE_NODE_CONNECTION);
boolean singleNodeConnectionOverride = singleNodeConnection != null && singleNodeConnection.trim().length() > 0;
String peers = bitcoinController.getModel().getUserPreference(BitcoinModel.PEERS);
boolean singlePeerOverride = peers != null && peers.split(",").length == 1;
boolean enableSend = false;
if (thisPanel.sendBitcoinNowAction != null) {
if (!singleNodeConnectionOverride && !singlePeerOverride && bitcoinController.getModel().getNumberOfConnectedPeers() < BitcoinModel.MINIMUM_NUMBER_OF_CONNECTED_PEERS_BEFORE_SEND_IS_ENABLED) {
// Disable send button
enableSend = false;
} else {
// Enable send button
enableSend = true;
}
if (bitcoinController.getModel().getActivePerWalletModelData().isBusy()) {
enableSend = false;
}
thisPanel.sendBitcoinNowAction.setEnabled(enableSend);
}
MultiBitLabel confirmText1 = thisPanel.confirmText1;
if (enableSend) {
if (confirmText1 != null) {
if (MultiBit.getController().getLocaliser()
.getString("sendBitcoinConfirmView.multibitMustBeOnline").equals(confirmText1.getText())) {
confirmText1.setText(" ");
}
}
} else {
if (confirmText1 != null) {
confirmText1.setText(MultiBit.getController().getLocaliser()
.getString("sendBitcoinConfirmView.multibitMustBeOnline"));
}
}
}
thisPanel.invalidate();
thisPanel.validate();
thisPanel.repaint();
}
}
});
}
public static void updatePanelDueToTransactionConfidenceChange(final Sha256Hash transactionWithChangedConfidenceHash,
final int numberOfPeersSeenBy) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (thisPanel == null || !thisPanel.isVisible() || thisPanel.getSendBitcoinNowAction() == null) {
return;
}
Transaction sentTransaction = thisPanel.getSendBitcoinNowAction().getTransaction();
if (sentTransaction == null || !sentTransaction.getHash().equals(transactionWithChangedConfidenceHash)) {
return;
}
MultiBitLabel confirmText2 = thisPanel.getConfirmText2();
if (confirmText2 != null) {
confirmText2.setText(thisPanel.getConfidenceToolTip(numberOfPeersSeenBy));
confirmText2.setIcon(thisPanel.getConfidenceIcon(numberOfPeersSeenBy));
}
thisPanel.invalidate();
thisPanel.validate();
thisPanel.repaint();
}
});
}
private String getConfidenceToolTip(int numberOfPeers) {
StringBuilder builder = new StringBuilder("");
builder
.append(MultiBit.getController().getLocaliser().getString("transactionConfidence.seenBy"))
.append(" ");
builder.append(numberOfPeers);
if (numberOfPeers == 1) {
builder.append(" ")
.append(MultiBit.getController().getLocaliser().getString("transactionConfidence.peer"))
.append(".");
} else {
builder
.append(" ")
.append(MultiBit.getController().getLocaliser().getString("transactionConfidence.peers"))
.append(".");
}
return builder.toString();
}
private ImageIcon getConfidenceIcon(int numberOfPeers) {
// By default return a triangle which indicates the least known.
ImageIcon iconToReturn;
if (numberOfPeers >= 4) {
return progress0Icon;
} else {
switch (numberOfPeers) {
case 0:
iconToReturn = shapeTriangleIcon;
break;
case 1:
iconToReturn = shapeSquareIcon;
break;
case 2:
iconToReturn = shapeHeptagonIcon;
break;
case 3:
iconToReturn = shapeHexagonIcon;
break;
default:
iconToReturn = shapeTriangleIcon;
}
}
return iconToReturn;
}
public MultiBitButton getCancelButton() {
return cancelButton;
}
// Used in testing.
public SendBitcoinNowAction getSendBitcoinNowAction() {
return sendBitcoinNowAction;
}
public String getMessageText1() {
return confirmText1.getText();
}
public String getMessageText2() {
return confirmText2.getText();
}
public void setWalletPassword(CharSequence password) {
walletPasswordField.setText(password.toString());
}
public boolean isWalletPasswordFieldEnabled() {
return walletPasswordField.isEnabled();
}
public MultiBitLabel getConfirmText2() {
return confirmText2;
}
@Override
public void walletBusyChange(boolean newWalletIsBusy) {
enableSendAccordingToNumberOfConnectedPeersAndWalletBusy();
}
}
SendBitcoinConfirmPanel类的对象主要是负责生成一个如下图所示的确认界面:
代码如下:
package org.multibit.viewsystem.swing.action;
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.Wallet.SendRequest;
import com.google.bitcoin.crypto.KeyCrypterException;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.multibit.controller.Controller;
import org.multibit.controller.bitcoin.BitcoinController;
import org.multibit.file.WalletSaveException;
import org.multibit.message.Message;
import org.multibit.message.MessageManager;
import org.multibit.model.bitcoin.*;
import org.multibit.viewsystem.swing.MultiBitFrame;
import org.multibit.viewsystem.swing.view.panels.SendBitcoinConfirmPanel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.nio.CharBuffer;
/**
* This {@link Action} actually spends bitcoin.
*/
public class SendBitcoinNowAction extends AbstractAction implements WalletBusyListener {
public Logger log = LoggerFactory.getLogger(SendBitcoinNowAction.class.getName());
private static final long serialVersionUID = 1913592460523457765L;
private final Controller controller;
private final BitcoinController bitcoinController;
private SendBitcoinConfirmPanel sendBitcoinConfirmPanel;
private JPasswordField walletPasswordField;
private final static int MAX_LENGTH_OF_ERROR_MESSAGE = 120;
/**
* Boolean to indicate that the test parameters should be used for "sending".
*/
private boolean useTestParameters = false;
/**
* Boolean to indicate that the "send was successful" or not (when useTestParameters = true).
*/
private boolean sayTestSendWasSuccessful = false;
private Transaction transaction;
private SendRequest sendRequest;
/**
* Creates a new {@link SendBitcoinNowAction}.
*/
public SendBitcoinNowAction(MultiBitFrame mainFrame, BitcoinController bitcoinController,
SendBitcoinConfirmPanel sendBitcoinConfirmPanel, JPasswordField walletPasswordField, ImageIcon icon, SendRequest sendRequest) {
super(bitcoinController.getLocaliser().getString("sendBitcoinConfirmAction.text"), icon);
this.bitcoinController = bitcoinController;
this.controller = this.bitcoinController;
this.sendBitcoinConfirmPanel = sendBitcoinConfirmPanel;
this.walletPasswordField = walletPasswordField;
this.sendRequest = sendRequest;
MnemonicUtil mnemonicUtil = new MnemonicUtil(controller.getLocaliser());
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("sendBitcoinConfirmAction.tooltip"));
putValue(MNEMONIC_KEY, mnemonicUtil.getMnemonic("sendBitcoinConfirmAction.mnemonicKey"));
// This action is a WalletBusyListener.
this.bitcoinController.registerWalletBusyListener(this);
walletBusyChange(this.bitcoinController.getModel().getActivePerWalletModelData().isBusy());
}
/**
* Actually send the bitcoin.
*/
@Override
public void actionPerformed(ActionEvent event) {
sendBitcoinConfirmPanel.setMessageText(" ", " ");
// Check to see if the wallet files have changed.
WalletData perWalletModelData = this.bitcoinController.getModel().getActivePerWalletModelData();
boolean haveFilesChanged = this.bitcoinController.getFileHandler().haveFilesChanged(perWalletModelData);
if (haveFilesChanged) {
// Set on the perWalletModelData that files have changed and fire data changed.
perWalletModelData.setFilesHaveBeenChangedByAnotherProcess(true);
this.bitcoinController.fireFilesHaveBeenChangedByAnotherProcess(perWalletModelData);
} else {
// Put sending message and remove the send button.
sendBitcoinConfirmPanel.setMessageText(controller.getLocaliser().getString("sendBitcoinNowAction.sendingBitcoin"), "");
// Get the label and address out of the wallet preferences.
//从钱包中获取一个发送比特币的地址和标签
String sendAddress = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_ADDRESS);
String sendLabel = this.bitcoinController.getModel().getActiveWalletPreference(BitcoinModel.SEND_LABEL);
if (sendLabel != null && !sendLabel.equals("")) {
WalletInfoData addressBook = perWalletModelData.getWalletInfo();
addressBook.addSendingAddress(new WalletAddressBookData(sendLabel, sendAddress));
}
char[] walletPassword = walletPasswordField.getPassword();
if (this.bitcoinController.getModel().getActiveWallet() != null
&& this.bitcoinController.getModel().getActiveWallet().getEncryptionType() != EncryptionType.UNENCRYPTED) {
// Encrypted wallet.
if (walletPassword == null || walletPassword.length == 0) {
// User needs to enter password.
sendBitcoinConfirmPanel.setMessageText(//输入钱包密码
controller.getLocaliser().getString("showExportPrivateKeysAction.youMustEnterTheWalletPassword"), "");
return;
}
try {
if (!this.bitcoinController.getModel().getActiveWallet().checkPassword(CharBuffer.wrap(walletPassword))) {
// The password supplied is incorrect.
sendBitcoinConfirmPanel.setMessageText(//钱包密码不对
controller.getLocaliser().getString("createNewReceivingAddressSubmitAction.passwordIsIncorrect"),
"");
return;
}
} catch (KeyCrypterException kce) {
log.debug(kce.getClass().getCanonicalName() + " " + kce.getMessage());
// The password supplied is probably incorrect.
sendBitcoinConfirmPanel.setMessageText(
controller.getLocaliser().getString("createNewReceivingAddressSubmitAction.passwordIsIncorrect"), "");
return;
}
}
// Double check wallet is not busy then declare that the active wallet is busy with the task
if (!perWalletModelData.isBusy()) {
perWalletModelData.setBusy(true);//正在发送 比特币
perWalletModelData.setBusyTaskVerbKey("sendBitcoinNowAction.sendingBitcoin");
this.bitcoinController.fireWalletBusyChange(true);
sendBitcoinConfirmPanel.setMessageText(controller.getLocaliser().getString("sendBitcoinNowAction.sendingBitcoin"), "");
sendBitcoinConfirmPanel.invalidate();
sendBitcoinConfirmPanel.validate();
sendBitcoinConfirmPanel.repaint();
performSend(perWalletModelData, sendRequest, CharBuffer.wrap(walletPassword));
}
}
}
/**
* Send the transaction directly.
*/
private void performSend(WalletData perWalletModelData, SendRequest sendRequest, CharSequence walletPassword) {
String message = null;
boolean sendWasSuccessful = Boolean.FALSE;
try {
if (sendRequest != null && sendRequest.tx != null) {
log.debug("Sending from wallet " + perWalletModelData.getWalletFilename() + ", tx = " + sendRequest.tx.toString());
}
if (useTestParameters) {
log.debug("Using test parameters - not really sending");
if (sayTestSendWasSuccessful) {
sendWasSuccessful = Boolean.TRUE;
log.debug("Using test parameters - saying send was successful");
} else {
message = "test - send failed";
log.debug("Using test parameters - saying send failed");
}
} else {
transaction = this.bitcoinController.getMultiBitService().sendCoins(perWalletModelData, sendRequest, walletPassword);
if (transaction == null) {
// a null transaction returned indicates there was not
// enough money (in spite of our validation)
message = controller.getLocaliser().getString("sendBitcoinNowAction.thereWereInsufficientFundsForTheSend");
log.error(message);
} else {
sendWasSuccessful = Boolean.TRUE;
log.debug("Sent transaction was:\n" + transaction.toString());
}
}
} catch (KeyCrypterException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (WalletSaveException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (IOException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (AddressFormatException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
} catch (IllegalStateException e) {
log.error(e.getMessage(), e);
message = controller.getLocaliser().getString("sendBitcoinNowAction.pingFailure");
} catch (Exception e) {
// Really trying to catch anything that goes wrong with the send bitcoin.
log.error(e.getMessage(), e);
message = e.getMessage();
} finally {
// Save the wallet.
try {
this.bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, false);
} catch (WalletSaveException e) {
log.error(e.getMessage(), e);
message = e.getMessage();
}
if (sendWasSuccessful) {
String successMessage = controller.getLocaliser().getString("sendBitcoinNowAction.bitcoinSentOk");
if (sendBitcoinConfirmPanel != null && (sendBitcoinConfirmPanel.isVisible() || useTestParameters)) {
sendBitcoinConfirmPanel.setMessageText(
controller.getLocaliser().getString("sendBitcoinNowAction.bitcoinSentOk"));
sendBitcoinConfirmPanel.showOkButton();
sendBitcoinConfirmPanel.clearAfterSend();
} else {
MessageManager.INSTANCE.addMessage(new Message(successMessage));
}
} else {
log.error(message);
if (message != null && message.length() > MAX_LENGTH_OF_ERROR_MESSAGE) {
message = message.substring(0, MAX_LENGTH_OF_ERROR_MESSAGE) + "...";
}
String errorMessage = controller.getLocaliser().getString("sendBitcoinNowAction.bitcoinSendFailed");
if (sendBitcoinConfirmPanel != null && (sendBitcoinConfirmPanel.isVisible() || useTestParameters)) {
sendBitcoinConfirmPanel.setMessageText(errorMessage, message);
} else {
MessageManager.INSTANCE.addMessage(new Message(errorMessage + " " + message));
}
}
// Declare that wallet is no longer busy with the task.
perWalletModelData.setBusyTaskKey(null);
perWalletModelData.setBusy(false);
this.bitcoinController.fireWalletBusyChange(false);
log.debug("firing fireRecreateAllViews...");
controller.fireRecreateAllViews(false);
log.debug("firing fireRecreateAllViews...done");
}
}
public Transaction getTransaction() {
return transaction;
}
void setTestParameters(boolean useTestParameters, boolean sayTestSendWasSuccessful) {
this.useTestParameters = useTestParameters;
this.sayTestSendWasSuccessful = sayTestSendWasSuccessful;
}
@Override
public void walletBusyChange(boolean newWalletIsBusy) {
// Update the enable status of the action to match the wallet busy status.
if (this.bitcoinController.getModel().getActivePerWalletModelData().isBusy()) {
// Wallet is busy with another operation that may change the private keys - Action is disabled.
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("multiBitSubmitAction.walletIsBusy",
new Object[]{controller.getLocaliser().getString(this.bitcoinController.getModel().getActivePerWalletModelData().getBusyTaskKey())}));
setEnabled(false);
} else {
// Enable unless wallet has been modified by another process.
if (!this.bitcoinController.getModel().getActivePerWalletModelData().isFilesHaveBeenChangedByAnotherProcess()) {
putValue(SHORT_DESCRIPTION, controller.getLocaliser().getString("sendBitcoinConfirmAction.tooltip"));
setEnabled(true);
}
}
}
}
而在SendBitcoinNowAction类的对象中
transaction = this.bitcoinController.getMultiBitService().sendCoins(perWalletModelData, sendRequest, walletPassword);
会调用
代码如下:
/**
* Send bitcoins from the active wallet.
*
* @return The sent transaction (may be null if there were insufficient
* funds for send)
* @throws KeyCrypterException
* @throws IOException
* @throws AddressFormatException
*/
public Transaction sendCoins(WalletData perWalletModelData, SendRequest sendRequest,
CharSequence password) throws java.io.IOException, AddressFormatException, KeyCrypterException {
// Ping the peers to check the bitcoin network connection
List connectedPeers = peerGroup.getConnectedPeers();
boolean atLeastOnePingWorked = false;
if (connectedPeers != null) {
for (Peer peer : connectedPeers) {
log.debug("Ping: {}", peer.getAddress().toString());
try {
ListenableFuture result = peer.ping();
result.get(4, TimeUnit.SECONDS);
atLeastOnePingWorked = true;
break;
} catch (ProtocolException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
} catch (InterruptedException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
} catch (ExecutionException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
} catch (TimeoutException e) {
log.warn("Peer '" + peer.getAddress().toString() + "' failed ping test. Message was " + e.getMessage());
}
}
}
if (!atLeastOnePingWorked) {
throw new IllegalStateException("All peers failed ping test (check network)");
}
// Send the coins
log.debug("MultiBitService#sendCoins - Just about to send coins");
KeyParameter aesKey = null;
if (perWalletModelData.getWallet().getEncryptionType() != EncryptionType.UNENCRYPTED) {
aesKey = perWalletModelData.getWallet().getKeyCrypter().deriveKey(password);
}
sendRequest.aesKey = aesKey;
sendRequest.fee = BigInteger.ZERO;
sendRequest.feePerKb = BitcoinModel.SEND_FEE_PER_KB_DEFAULT;
sendRequest.tx.getConfidence().addEventListener(perWalletModelData.getWallet().getTxConfidenceListener());
try {
// The transaction is already added to the wallet (in SendBitcoinConfirmAction) so here we just need
// to sign it, commit it and broadcast it.
perWalletModelData.getWallet().sign(sendRequest);//签名
perWalletModelData.getWallet().commitTx(sendRequest.tx);//提交
// The tx has been committed to the pending pool by this point (via sendCoinsOffline -> commitTx), so it has
// a txConfidenceListener registered. Once the tx is broadcast the peers will update the memory pool with the
// count of seen peers, the memory pool will update the transaction confidence object, that will invoke the
// txConfidenceListener which will in turn invoke the wallets event listener onTransactionConfidenceChanged
// method.
peerGroup.broadcastTransaction(sendRequest.tx);//广播
log.debug("Sending transaction '" + Utils.bytesToHexString(sendRequest.tx.bitcoinSerialize()) + "'");
} catch (VerificationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Transaction sendTransaction = sendRequest.tx;
log.debug("MultiBitService#sendCoins - Sent coins has completed");
assert sendTransaction != null;
// We should never try to send more coins than we have!
// throw an exception if sendTransaction is null - no money.
if (sendTransaction != null) {
log.debug("MultiBitService#sendCoins - Sent coins. Transaction hash is {}", sendTransaction.getHashAsString() + ", identityHashcode = " + System.identityHashCode(sendTransaction));
if (sendTransaction.getConfidence() != null) {
log.debug("Added bitcoinController " + System.identityHashCode(bitcoinController) + " as listener to tx = " + sendTransaction.getHashAsString());
sendTransaction.getConfidence().addEventListener(bitcoinController);
} else {
log.debug("Cannot add bitcoinController as listener to tx = " + sendTransaction.getHashAsString() + " no transactionConfidence");
}
try {
bitcoinController.getFileHandler().savePerWalletModelData(perWalletModelData, false);
} catch (WalletSaveException wse) {
log.error(wse.getClass().getCanonicalName() + " " + wse.getMessage());
MessageManager.INSTANCE.addMessage(new Message(wse.getClass().getCanonicalName() + " " + wse.getMessage()));
} catch (WalletVersionException wse) {
log.error(wse.getClass().getCanonicalName() + " " + wse.getMessage());
MessageManager.INSTANCE.addMessage(new Message(wse.getClass().getCanonicalName() + " " + wse.getMessage()));
}
try {
// Notify other wallets of the send (it might be a send to or from them).
List perWalletModelDataList = bitcoinController.getModel().getPerWalletModelDataList();
if (perWalletModelDataList != null) {
for (WalletData loopPerWalletModelData : perWalletModelDataList) {
if (!perWalletModelData.getWalletFilename().equals(loopPerWalletModelData.getWalletFilename())) {
Wallet loopWallet = loopPerWalletModelData.getWallet();
if (loopWallet.isPendingTransactionRelevant(sendTransaction)) {
// The loopPerWalletModelData is marked as dirty.
if (loopPerWalletModelData.getWalletInfo() != null) {
synchronized (loopPerWalletModelData.getWalletInfo()) {
loopPerWalletModelData.setDirty(true);
}
} else {
loopPerWalletModelData.setDirty(true);
}
if (loopWallet.getTransaction(sendTransaction.getHash()) == null) {
log.debug("MultiBit adding a new pending transaction for the wallet '"
+ loopPerWalletModelData.getWalletDescription() + "'\n" + sendTransaction.toString());
loopWallet.receivePending(sendTransaction, null);
}
}
}
}
}
} catch (ScriptException e) {
e.printStackTrace();
} catch (VerificationException e) {
e.printStackTrace();
}
}
return sendTransaction;
}
通过sendCoins方法,完成对transaction的签名和广播