Multibit原始碼解析學習篇之---傳送比特幣
阿新 • • 發佈:2019-01-01
1)首先驗證所填寫的接受比特幣的地址是否合法 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); } }
即在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類的物件
生成的方法中又會建立一個SendBitcoinConfirmPanel類的物件程式碼如下: 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); } }
程式碼如下:
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類的物件主要是負責生成一個如下圖所示的確認介面:當點擊發送按鈕後,會呼叫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<Peer> connectedPeers = peerGroup.getConnectedPeers();
boolean atLeastOnePingWorked = false;
if (connectedPeers != null) {
for (Peer peer : connectedPeers) {
log.debug("Ping: {}", peer.getAddress().toString());
try {
ListenableFuture<Long> 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<WalletData> 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的簽名和廣播