-
Notifications
You must be signed in to change notification settings - Fork 6
/
PendingTransactionContainer.java
219 lines (211 loc) · 10 KB
/
PendingTransactionContainer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
* Curecoin 2.0.0a Source Code
* Copyright (c) 2015 Curecoin Developers
* Distributed under MIT License
* Requires Apache Commons Library
* Supports Java 1.7+
*/
import java.util.*;
/**
* This class offers basic functionality for storing transactions until they make it into a block.
* It could be just an ArrayList<String> inside of MainClass, however it seemed easier and more OOP-ish to give it its own object.
* Adding future functionality to pending transaction pool management is much easier when it has its own object.
*/
public class PendingTransactionContainer
{
public ArrayList<String> pendingTransactions;
private CurecoinDatabaseMaster databaseMaster;
//ArrayList holding objects that pair addresses with their pending transaction amounts, so transactions above an account's spendable balance are rejected.
public ArrayList<StringLongPair> accountBalanceDeltaTables;
/**
* Constructor for PendingTransactionContainer sets up required ArrayList for holding transactions. The database manager object is passed in, for checking balances
* when a transaction is being added.
*/
public PendingTransactionContainer(CurecoinDatabaseMaster databaseMaster)
{
this.databaseMaster = databaseMaster;
this.pendingTransactions = new ArrayList<String>();
this.accountBalanceDeltaTables = new ArrayList<StringLongPair>();
}
/**
* Adds a transaction to the pending transaction list if it is formatted correctly and accompanied by a correct signature. Does not check for account balances!
* Rejects duplicate transactions.
* Transaction format:
* InputAddress;InputAmount;OutputAddress1;OutputAmount1;OutputAddress2;OutputAmount2...;SignatureData;SignatureIndex
* Additional work in the future on this method will include keeping track of signature indexes and prioritizing lower-index transactions.
*
* @param transaction Transaction to add
*
* @return boolean Whether adding the transaction was valid
*/
public boolean addTransaction(String transaction)
{
try
{
for (int i = 0; i < pendingTransactions.size(); i++)
{
if (pendingTransactions.get(i).equals(transaction))
{
return false;
}
}
if (!TransactionUtility.isTransactionValid(transaction))
{
System.out.println("Throwing out a transaction deemed invalid");
return false;
}
String[] transactionParts = transaction.split(";");
//We need to check to make sure the input address isn't sending coins they don't own.
String inputAddress = transactionParts[0];
long inputAmount = Long.parseLong(transactionParts[1]);
//Check for the outstanding outgoing amount for this address
long outstandingOutgoingAmount = 0L;
int indexOfDelta = -1;
for (int i = 0; i < accountBalanceDeltaTables.size(); i++)
{
if (accountBalanceDeltaTables.get(i).stringToHold.equals(inputAddress))
{
outstandingOutgoingAmount = accountBalanceDeltaTables.get(i).longToHold;
indexOfDelta = i;
break;
}
}
long previousBalance = databaseMaster.getAddressBalance(inputAddress);
if (previousBalance < inputAmount + outstandingOutgoingAmount)
{
System.out.println("Account " + inputAddress + " tried to spend " + inputAmount + " but only had " + (previousBalance - outstandingOutgoingAmount) + " coins.");
return false; //Account does not have the coins to spend!
}
if (indexOfDelta >= 0)
{
accountBalanceDeltaTables.get(indexOfDelta).longToHold += inputAmount;
}
else
{
accountBalanceDeltaTables.add(new StringLongPair(inputAddress, inputAmount)); //No existing entry in the pending delta tables, so we create an ew one
}
pendingTransactions.add(transaction); //Can only get to here if the transaction is valid, accounted for, and the balance checks out.
System.out.println("Added transaction " + transaction.substring(0, 20) + "..." + transaction.substring(transaction.length() - 20, transaction.length()));
} catch (Exception e)
{
System.out.println("An exception has occurred...");
e.printStackTrace();
return false;
//e.printStackTrace();
}
return true;
}
/**
* Self-explanatory method called whenever the daemon desires to reset the pending transaction pool to be blank.
*/
public void reset()
{
pendingTransactions = new ArrayList<String>();
accountBalanceDeltaTables = new ArrayList<StringLongPair>();
}
/**
* Removes an identical transaction from the pending transactions pool
*
* @param transaction The transaction to remove
*
* @return boolean Whether removal was successful
*/
public boolean removeTransaction(String transaction)
{
for (int i = 0; i < pendingTransactions.size(); i++)
{
if (pendingTransactions.get(i).equals(transaction))
{
pendingTransactions.remove(i);
return true;
}
}
return false; //Transaction was not found in pending transaction pool
}
/**
* This method is the most useful method in this class--it allows the mass removal of all transactions from the pending transaction pool that were included
* in a network block, all in one call. The returned boolean is not currently utilized in MainClass, proper handling of blocks with transaction issues will be addressed
* in a future alpha, probably 2.0.0a6/7 given my schedule.
*
* @param rawBlock The raw String representing the block holding transactions to remove
*
* @return boolean Whether all transactions in the block were successfully removed
*/
public boolean removeTransactionsInBlock(String rawBlock)
{
//This try-catch method wraps around more than it needs to, in the name of easy code management, and making colors line up nicely in my IDE.
try
{
//We could use the raw String data, but it's easier to use a Block object to avoid repetition of code, and the verification is an added bonus.
Block tempBlock = new Block(rawBlock);
/* Transaction format:
* InputAddress;InputAmount;OutputAddress1;OutputAmount1;OutputAddress2;OutputAmount2...;SignatureData;SignatureIndex
*
* We are removing only transactions that match the exact String from the block. If the block validation fails, NO transactions are removed from the pool.
* In a late-night coding session, not removing any transactions of an invalid block seemed like the bset idea--transactions should never be discarded
* if they haven't made it into the blockchain, and any block that doesn't validate won't make it through Blockchain's block screening, so these transactions
* that we aren't removing will never happen on-chain if we remove them from the pool when an invalid block says we should. Also closes a potential attack
* vector where someone could submit false blocks in order to be a nuisance and empty the pending transaction pool.
*/
if (!tempBlock.validateBlock(databaseMaster.blockchain))
{
return false; //No transactions remove at all!
}
ArrayList<String> transactions = tempBlock.transactions;
boolean allSuccessful = true;
for (int i = 0; i < transactions.size(); i++)
{
if (!removeTransaction(transactions.get(i)))
{
allSuccessful = false; //This might happen if a transaction was in a block before it made it across the network to a peer, so not always a big deal!
}
}
return allSuccessful;
} catch (Exception e)
{
e.printStackTrace();
return false;
}
}
/**
* This method scans through all of the pending transactions to calculate the total (net) balance change pending on an address. A negative value represents
* coins that were sent from the address in question, and a positive value represents coins awaiting confirmations to arrive.
*
* @param address Curecoin 2.0 address to search the pending transaction pool for
*
* @return long The pending total (net) change for the address in question
*/
public long getPendingBalance(String address)
{
long totalChange = 0L;
for (int i = 0; i < pendingTransactions.size(); i++)
{
String transaction = pendingTransactions.get(i);
try
{
if (transaction.contains(address))
{
String[] transactionParts = transaction.split(";");
String senderAddress = transactionParts[0];
if (senderAddress.equals(address))
{
totalChange -= Long.parseLong(transactionParts[1]);
}
for (int j = 2; j < transactionParts.length - 2; j+=2)
{
if (transactionParts[j].equals(address))
{
totalChange += Long.parseLong(transactionParts[j+1]);
}
}
}
} catch (Exception e)
{
e.printStackTrace();
System.err.println("Major problem: Transaction in the pending transaction pool is incorrectly formatted!");
System.err.println("Transaction in question: " + transaction);
}
}
return totalChange;
}
}