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
This commit is contained in:
romanman 2014-05-25 14:52:45 +03:00
parent 0c01a4fde6
commit dbbfe450e5
13 changed files with 304 additions and 47 deletions

View File

@ -62,6 +62,13 @@ public class SystemProperties {
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 "";
return prop.getProperty("client.name");

View File

@ -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;

View File

@ -39,6 +39,8 @@ public class Wallet {
private List<WalletListener> listeners = new ArrayList<WalletListener>();
private HashMap<BigInteger, Transaction> transactionMap = new HashMap<BigInteger, Transaction>();
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<Transaction> 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;
}
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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<PeerData> peers = Collections.synchronizedList(new ArrayList<PeerData>());
private List<Block> blockChainDB = new ArrayList<Block>();
private Wallet wallet = new Wallet();
private ClientPeer activePeer;
private Map<BigInteger, PendingTransaction> pendingTransactions =
Collections.synchronizedMap(new HashMap<BigInteger, PendingTransaction>());
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<Transaction> 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<PeerData> getPeers() {

View File

@ -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());
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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