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:
parent
0c01a4fde6
commit
dbbfe450e5
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
/*
|
||||
* 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() {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue