From dbbfe450e5d1a2d320aba543bcaaea01e0b5a4b9 Mon Sep 17 00:00:00 2001 From: romanman Date: Sun, 25 May 2014 14:52:45 +0300 Subject: [PATCH] PendingTransaction mechanism introduced: 1. the dialog put a pending transaction on the list 2. the dialog send the transaction to a net 3. wherever the transaction got for the wire in will change to approve state 4. only after the approve a Wallet state changes 5. After the block is received with that tx the pending been clean up --- .../org/ethereum/config/SystemProperties.java | 7 ++ .../java/org/ethereum/core/Transaction.java | 1 + .../main/java/org/ethereum/core/Wallet.java | 46 +++++++--- .../ethereum/gui/ConnectionConsoleWindow.java | 4 +- .../main/java/org/ethereum/gui/GUIUtils.java | 34 +++++++ .../java/org/ethereum/gui/PayOutDialog.java | 92 ++++++++++++++++--- .../java/org/ethereum/manager/MainData.java | 51 +++++++--- .../net/client/EthereumProtocolHandler.java | 6 +- .../net/submit/PendingTransaction.java | 26 ++++++ .../net/submit/TransactionExecutor.java | 24 +++++ .../ethereum/net/submit/TransactionTask.java | 51 ++++++++++ .../src/main/resources/log4j.properties | 2 +- .../src/main/resources/system.properties | 7 ++ 13 files changed, 304 insertions(+), 47 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/net/submit/PendingTransaction.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionExecutor.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java diff --git a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java index 97d9e9e2..7d381c6d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java +++ b/ethereumj-core/src/main/java/org/ethereum/config/SystemProperties.java @@ -61,6 +61,13 @@ public class SystemProperties { return 10000; return Integer.parseInt(prop.getProperty("peer.discovery.timeout")) * 1000; } + + public int transactionApproveTimeout(){ + if (prop.isEmpty()) + return 10; + return Integer.parseInt(prop.getProperty("transaction.approve.timeout")); + } + public String clientName() { if(prop.isEmpty()) return ""; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java index 16f25778..70978aa9 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.BigIntegers; +import java.math.BigInteger; import java.security.SignatureException; import java.util.Arrays; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java b/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java index ace93ff9..21931417 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Wallet.java @@ -39,6 +39,8 @@ public class Wallet { private List listeners = new ArrayList(); + private HashMap transactionMap = new HashMap(); + public void addNewKey(){ AddressState addressState = new AddressState(); @@ -86,31 +88,45 @@ public class Wallet { } + public void applyTransaction(Transaction transaction){ + + transactionMap.put(new BigInteger(transaction.getHash()), transaction ); + + byte[] senderAddress = transaction.getSender(); + AddressState senderState = rows.get(Hex.toHexString(senderAddress)); + if (senderState != null){ + + BigInteger value = new BigInteger(transaction.getValue()); + senderState.addToBalance(value.negate()); + senderState.incrementTheNonce(); + } + + byte[] receiveAddress = transaction.getReceiveAddress(); + AddressState receiverState = rows.get(Hex.toHexString(receiveAddress)); + if (receiverState != null){ + receiverState.addToBalance(new BigInteger(1, transaction.getValue())); + } + + notifyListeners(); + } + + public void processBlock(Block block){ - boolean walletUpdated = false; // todo: proceed coinbase when you are the miner that gets an award + boolean walletUpdated = false; + List transactions = block.getTransactionsList(); for (Transaction tx : transactions){ + boolean txExist = transactionMap.get(new BigInteger(tx.getHash())) != null; + if (txExist) break; - byte[] senderAddress = tx.getSender(); - AddressState senderState = rows.get(Hex.toHexString(senderAddress)); - if (senderState != null){ - BigInteger value = new BigInteger(tx.getValue()); + else { - senderState.addToBalance(value.negate()); - - senderState.incrementTheNonce(); - walletUpdated = true; - } - - byte[] receiveAddress = tx.getReceiveAddress(); - AddressState receiverState = rows.get(Hex.toHexString(receiveAddress)); - if (receiverState != null){ - receiverState.addToBalance(new BigInteger(1, tx.getValue())); + applyTransaction(tx); walletUpdated = true; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java b/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java index bb9360a0..9d616601 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/ConnectionConsoleWindow.java @@ -73,7 +73,7 @@ public class ConnectionConsoleWindow extends JFrame implements PeerListener{ // new ClientPeer(thisConsole).connect("54.201.28.117", 30303); // Peer Server One: peer discovery - new ClientPeer(thisConsole).connect("54.204.10.41", 30303); +// new ClientPeer(thisConsole).connect("54.204.10.41", 30303); // Some dude in Canada // new ClientPeer(thisConsole).connect("131.104.247.135", 30303); @@ -85,7 +85,7 @@ public class ConnectionConsoleWindow extends JFrame implements PeerListener{ // new ClientPeer(thisConsole).connect("54.204.10.41", 30303); // RomanJ -// new ClientPeer(thisConsole).connect("54.211.14.10", 40404); + new ClientPeer(thisConsole).connect("54.211.14.10", 40404); } }; t.start(); diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java b/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java index 8d5b46aa..253e5a7e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/GUIUtils.java @@ -28,4 +28,38 @@ public class GUIUtils { textField.setForeground(new Color(143, 170, 220)); textField.setFont(new Font("Monospaced", 0, 13)); } + + public static void addStyle(JTextArea textArea, String labelName, boolean isBorder){ + + Border border = null; + if (isBorder) { + Border line = BorderFactory.createLineBorder(Color.LIGHT_GRAY); + TitledBorder titled = BorderFactory.createTitledBorder(line, labelName); + titled.setTitleFont(new Font("Verdana", 0, 13)); + titled.setTitleColor(new Color(213, 225, 185)); + Border empty = new EmptyBorder(5, 8, 5, 8); + CompoundBorder cBorder = new CompoundBorder(titled, empty); + } + + textArea.setBorder(border); + textArea.setForeground(new Color(143, 170, 220)); + textArea.setFont(new Font("Monospaced", 0, 13)); + } + + public static void addStyle(JScrollPane jScrollPane, String labelName){ + Border line = BorderFactory.createLineBorder(Color.LIGHT_GRAY); + TitledBorder titled = BorderFactory.createTitledBorder(line, labelName); + titled.setTitleFont(new Font("Verdana", 0, 13)); + titled.setTitleColor(new Color(213, 225, 185)); + Border empty = new EmptyBorder(5, 8, 5, 8); + CompoundBorder border = new CompoundBorder(titled, empty); + jScrollPane.setBorder(border); + jScrollPane.setForeground(new Color(143, 170, 220)); + jScrollPane.setBackground(Color.WHITE); + jScrollPane.setFont(new Font("Monospaced", 0, 13)); + jScrollPane.setHorizontalScrollBar(null); + } + + + } diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java index 8b0129b3..fd948f45 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/PayOutDialog.java @@ -1,11 +1,15 @@ package org.ethereum.gui; +import org.ethereum.config.SystemProperties; import org.ethereum.core.Transaction; import org.ethereum.manager.MainData; import org.ethereum.net.client.ClientPeer; +import org.ethereum.net.submit.TransactionExecutor; +import org.ethereum.net.submit.TransactionTask; import org.ethereum.wallet.AddressState; import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; +import samples.Main; import java.awt.*; import java.awt.event.ActionEvent; @@ -14,12 +18,20 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.math.BigInteger; import java.net.URL; +import java.util.*; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; +import static org.ethereum.config.SystemProperties.CONFIG; + /** * www.ethereumJ.com * User: Roman Mandeleil @@ -83,7 +95,6 @@ class PayOutDialog extends JDialog { }} ); - URL approveIconURL = ClassLoader.getSystemResource("buttons/approve.png"); ImageIcon approveIcon = new ImageIcon(approveIconURL); JLabel approveLabel = new JLabel(approveIcon); @@ -94,7 +105,6 @@ class PayOutDialog extends JDialog { this.getContentPane().add(approveLabel); approveLabel.setVisible(true); - approveLabel.addMouseListener( new MouseAdapter() { @Override @@ -131,17 +141,18 @@ class PayOutDialog extends JDialog { tx.sign(senderPrivKey); } catch (Exception e1) { - // todo something if sign fails - e1.printStackTrace(); + dialog.alertStatusMsg("Failed to sign the transaction"); + return; } - peer.sendTransaction(tx); - dialog.infoStatusMsg("Transaction sent to the network, waiting for approve"); +// SwingWorker + + DialogWorker worker = new DialogWorker(tx); + worker.execute(); } } ); - feeInput.setText("1000"); amountInput.setText("0"); @@ -178,14 +189,69 @@ class PayOutDialog extends JDialog { return rootPane; } - public void infoStatusMsg(String text){ - this.statusMsg.setForeground(Color.GREEN.darker().darker()); - this.statusMsg.setText(text); + public void infoStatusMsg(final String text){ + + final PayOutDialog dialog = this; + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + dialog.statusMsg.setForeground(Color.GREEN.darker().darker()); + dialog.statusMsg.setText(text); + dialog.revalidate(); + dialog.repaint(); + } + }); + } - public void alertStatusMsg(String text){ - this.statusMsg.setForeground(Color.RED); - this.statusMsg.setText(text); + public void alertStatusMsg(final String text){ + final PayOutDialog dialog = this; + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + dialog.statusMsg.setForeground(Color.RED); + dialog.statusMsg.setText(text); + dialog.revalidate(); + dialog.repaint(); + } + }); + } + + + class DialogWorker extends SwingWorker{ + + Transaction tx; + + DialogWorker(Transaction tx) { + this.tx = tx; + } + + @Override + protected Object doInBackground() throws Exception { + TransactionTask transactionTask = new TransactionTask(tx); + Future future = TransactionExecutor.instance.submitTransaction(transactionTask); + dialog.infoStatusMsg("Transaction sent to the network, waiting for approve"); + + try { + future.get(CONFIG.transactionApproveTimeout(), TimeUnit.SECONDS); + } catch (TimeoutException e1) { + e1.printStackTrace(); + dialog.alertStatusMsg("Transaction wasn't approved, network timeout"); + return null; + } catch (InterruptedException e1) { + e1.printStackTrace(); + dialog.alertStatusMsg("Transaction wasn't approved"); + return null; + } catch (ExecutionException e1) { + e1.printStackTrace(); + dialog.alertStatusMsg("Transaction wasn't approved"); + return null; + } + + dialog.infoStatusMsg("Transaction got approved"); + MainData.instance.getWallet().applyTransaction(tx); + return null; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java index 94bd7291..5414e66e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/MainData.java @@ -1,10 +1,7 @@ package org.ethereum.manager; import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; import com.maxmind.geoip.Location; import org.ethereum.core.Block; @@ -18,7 +15,10 @@ import org.ethereum.net.client.ClientPeer; import org.ethereum.net.client.PeerData; import org.ethereum.net.message.StaticMessages; import org.ethereum.net.peerdiscovery.PeerDiscovery; +import org.ethereum.net.submit.PendingTransaction; import org.ethereum.wallet.AddressState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; /** @@ -28,11 +28,16 @@ import org.spongycastle.util.encoders.Hex; */ public class MainData { + Logger logger = LoggerFactory.getLogger(getClass().getName()); + private List peers = Collections.synchronizedList(new ArrayList()); private List blockChainDB = new ArrayList(); private Wallet wallet = new Wallet(); private ClientPeer activePeer; + private Map pendingTransactions = + Collections.synchronizedMap(new HashMap()); + PeerDiscovery peerDiscovery; public static MainData instance = new MainData(); @@ -67,7 +72,6 @@ public class MainData { // check that the parent is the genesis if (blockChainDB.isEmpty() && !Arrays.equals(StaticMessages.GENESIS_HASH, firstBlockToAdd.getParentHash())){ - return; } @@ -86,7 +90,16 @@ public class MainData { wallet.processBlock(block); } - System.out.println("*** Block chain size: [" + blockChainDB.size() + "]"); + // Remove all pending transactions as they already approved by the net + for (Block block : blocks){ + for (Transaction tx : block.getTransactionsList()){ + + if (logger.isDebugEnabled()) + logger.debug("pending cleanup: tx.hash: [{}]", Hex.toHexString( tx.getHash())); + pendingTransactions.remove(tx.getHash()); + } + } + logger.info("*** Block chain size: [ {} ]", blockChainDB.size()); } public byte[] getLatestBlockHash(){ @@ -113,18 +126,28 @@ public class MainData { } - /* todo: here will be all the pending transaction approve - * 1) the dialog put a pending transaction her - * 2) the dialog send the transaction to a net - * 3) wherever the transaction got for the wire in will change to approve state - * 4) only after the approve a) Wallet state changes - * - * 5) After the block is received with that tx the pending been clean up + /* + * 1) the dialog put a pending transaction on the list + * 2) the dialog send the transaction to a net + * 3) wherever the transaction got for the wire in will change to approve state + * 4) only after the approve a) Wallet state changes + * + * 5) After the block is received with that tx the pending been clean up */ - public void addTransactions(List transactions) { + public PendingTransaction addPendingTransaction(Transaction transaction) { + BigInteger hash = new BigInteger(transaction.getHash()); + PendingTransaction pendingTransaction = pendingTransactions.get(hash); + if (pendingTransaction != null) + pendingTransaction.incApproved(); + else{ + pendingTransaction = new PendingTransaction(transaction); + pendingTransactions.put(hash, pendingTransaction); + } + + return pendingTransaction; } public List getPeers() { diff --git a/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java b/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java index 0a3fb41e..ce126bd0 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/client/EthereumProtocolHandler.java @@ -12,6 +12,7 @@ import java.util.Timer; import java.util.TimerTask; import org.ethereum.core.Block; +import org.ethereum.core.Transaction; import org.ethereum.gui.PeerListener; import org.ethereum.manager.MainData; import org.ethereum.net.Command; @@ -200,9 +201,10 @@ public class EthereumProtocolHandler extends ChannelInboundHandlerAdapter { RLPList rlpList = RLP.decode2(payload); TransactionsMessage transactionsMessage = new TransactionsMessage(rlpList); - MainData.instance.addTransactions(transactionsMessage.getTransactions()); + for (Transaction tx : transactionsMessage.getTransactions()) + MainData.instance.addPendingTransaction(tx); - // todo: if you got transactions send it to your peers + // todo: if you got transactions send it to your connected peers logger.info(transactionsMessage.toString()); if (peerListener != null) peerListener.console(transactionsMessage.toString()); } diff --git a/ethereumj-core/src/main/java/org/ethereum/net/submit/PendingTransaction.java b/ethereumj-core/src/main/java/org/ethereum/net/submit/PendingTransaction.java new file mode 100644 index 00000000..56f50af2 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/submit/PendingTransaction.java @@ -0,0 +1,26 @@ +package org.ethereum.net.submit; + +import org.ethereum.core.Transaction; + +/** + * www.ethereumJ.com + * User: Roman Mandeleil + * Created on: 23/05/2014 18:41 + */ + +public class PendingTransaction { + + Transaction tx; + int approved = 0; // each time the tx got from the wire this value increased + + public PendingTransaction(Transaction tx) { + this.tx = tx; + } + + public void incApproved(){++this.approved;} + + public int getApproved() { + return approved; + } +} + diff --git a/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionExecutor.java new file mode 100644 index 00000000..8d3c2338 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionExecutor.java @@ -0,0 +1,24 @@ +package org.ethereum.net.submit; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/** + * www.ethereumJ.com + * User: Roman Mandeleil + * Created on: 23/05/2014 19:07 + */ + +public class TransactionExecutor { + + static {instance = new TransactionExecutor();} + public static TransactionExecutor instance; + + ExecutorService executor = Executors.newFixedThreadPool(1); + + public Future submitTransaction(TransactionTask task){ + return executor.submit(task); + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java b/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java new file mode 100644 index 00000000..94336b09 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/net/submit/TransactionTask.java @@ -0,0 +1,51 @@ +package org.ethereum.net.submit; + +import org.ethereum.core.Transaction; +import org.ethereum.manager.MainData; +import org.ethereum.net.client.ClientPeer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Callable; + +import static java.lang.Thread.sleep; + +/** + * www.ethereumJ.com + * User: Roman Mandeleil + * Created on: 23/05/2014 18:33 + */ + +public class TransactionTask implements Callable { + + Logger logger = LoggerFactory.getLogger("TransactionTask"); + + Transaction tx; + + + public TransactionTask(Transaction tx) { + this.tx = tx; + } + + @Override + public Object call() throws Exception { + + logger.info("call() tx: {}", tx.toString()); + + ClientPeer peer = MainData.instance.getActivePeer(); + + PendingTransaction pendingTransaction = MainData.instance.addPendingTransaction(tx); + peer.sendTransaction(tx); + + int i = 0; + while(pendingTransaction.getApproved() < 1 ){ + + ++i; + sleep(10); + } + + logger.info("return approved: {}", pendingTransaction.getApproved()); + + return null; + } +} diff --git a/ethereumj-core/src/main/resources/log4j.properties b/ethereumj-core/src/main/resources/log4j.properties index d988692f..1118f65b 100644 --- a/ethereumj-core/src/main/resources/log4j.properties +++ b/ethereumj-core/src/main/resources/log4j.properties @@ -1,5 +1,5 @@ # Root logger option -log4j.rootLogger=INFO, stdout +log4j.rootLogger=DEBUG, stdout # Direct log messages to stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender diff --git a/ethereumj-core/src/main/resources/system.properties b/ethereumj-core/src/main/resources/system.properties index 95923bfe..ad73bcb5 100644 --- a/ethereumj-core/src/main/resources/system.properties +++ b/ethereumj-core/src/main/resources/system.properties @@ -27,3 +27,10 @@ peer.discovery.workers = 5 # connection timeout for trying to # connect to a peer [seconds] peer.discovery.timeout = 3 + +# the time we wait to the network +# to approve the transaction, the +# transaction got approved when +# include into a transactions msg +# retrieved from the peer [seconds] +transaction.approve.timeout = 3 \ No newline at end of file