Multibit源码解析学习篇之---发送比特币

一 、发送比特币:
1.对transaction签完名并进行broadcast的代码实现部分:
multibit-master项目中/src/main/java/文件夹下 org.multibit.network包里MultiBitService类的sendCoins()方法-----570行
代码如下:
/**
 * 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.国际化部分发送按钮:

src/main/resources/i18n/zh/viewer.properties
sendBitcoinConfirmAction.text=发送-----193行
3.分析发送过程:
现象:当按下发送按钮给一个地址发送比特币,但是所填写的信息有误,会报错!
说明:按下按钮后发送比特币之前会在代码中对所填写信息进行有效性验证
如下图所示:
Multibit源码解析学习篇之---发送比特币_第1张图片
具体分析:
按下发送按钮后发送比特币之前进行的验证在
src/main/java文件下org.multibit.viewsystem.swing.action包里SendBitcoinConfirmAction类的actionPerformed方法进行验证
    /**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);
        }
    }
1)首先验证所填写的接受比特币的地址是否合法
2)然后验证所填写的发送金额是否超出发送方钱包的总额,发送金额是否为负数,是否为零等合法性信息
即在SendBitcoinConfirmAction类的actionPerformed方法的方法中调用validator.validate(sendAddress, sendAmount)方法
代码如下:
    /**
     * 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确认对话框
创建一个SendBitcoinConfirmDialog类的对象
代码如下:
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类的对象主要是负责生成一个如下图所示的确认界面:
Multibit源码解析学习篇之---发送比特币_第2张图片
当点击发送按钮后,会调用SendBitcoinNowAction类的对象
代码如下:
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);
会调用

中的sendCoins方法
代码如下:
  /**
   * 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的签名和广播

二、windows上安装Multibit后,数据默认存放在%Appdata%Roaming\Multibit下

打赏地址:13dyX9hbF9d1VQsCYLN5KHmq3uaQEdqNMz

你可能感兴趣的:(bitcoin)