-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathMainClass.java
739 lines (726 loc) · 47.8 KB
/
MainClass.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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
/*
* 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.io.*;
import java.awt.*;
import java.util.*;
/**
* Welcome to the Curecoin 2.0.0a5 source code! MainClass stitches all of the separate components together to make everything work.
* Quick overview of the program's structure:
* CurecoinDatabaseManager handles all database-related workloads, such as storing/adding blocks, account balance lookups, etc.
* PendingTransactionContainer is simply a glorified ArrayList<String> to hold pending transactions.
* PeerNetwork handles all P2P networking, delegating network workloads to PeerThread, which delegates to InputThread and OutputThread.
* RPC handles all RPC calls. At this point, RPC holds the most recent RPC call in a String, and MainClass is expected to loop quickly enough
* to grab RPC calls and plop the appropriate response into a object variable.
*/
public class MainClass
{
public static void main(String[] args)
{
launch();
//Start of the program, initialize Database object
CurecoinDatabaseMaster databaseMaster = new CurecoinDatabaseMaster("database");
PendingTransactionContainer pendingTransactionContainer = new PendingTransactionContainer(databaseMaster);
PeerNetwork peerNetwork = new PeerNetwork();
peerNetwork.start();
RPC rpcAgent = new RPC();
rpcAgent.start();
File peerFile = new File("peers.lst");
ArrayList<String> peers = new ArrayList<String>();
AddressManager addressManager = new AddressManager();
if (!peerFile.exists())
{
try
{
PrintWriter out = new PrintWriter(peerFile);
out.println("155.94.254.14:8015");
/*
* In future networks, these will route to servers running the daemon. For now, it's just the above IP.
*/
out.close();
} catch (Exception e)
{
e.printStackTrace();
}
}
try
{
Scanner scan = new Scanner(peerFile);
while (scan.hasNextLine())
{
String combo = scan.nextLine();
peers.add(combo);
String host = combo.substring(0, combo.indexOf(":"));
int port = Integer.parseInt(combo.substring(combo.indexOf(":") + 1));
peerNetwork.connectToPeer(host, port);
}
scan.close();
Thread.sleep(2000);
} catch (Exception e)
{
e.printStackTrace();
}
peerNetwork.broadcast("REQUEST_NET_STATE");
int topBlock = 0;
ArrayList<String> allBroadcastTransactions = new ArrayList<String>();
ArrayList<String> allBroadcastBlocks = new ArrayList<String>();
boolean catchupMode = true;
while (true)
{
//Look for new peers
if (peerNetwork.newPeers.size() > 0)
{
for (int i = 0; i < peerNetwork.newPeers.size(); i++)
{
if (peers.indexOf(peerNetwork.newPeers.get(i)) < 0)
peers.add(peerNetwork.newPeers.get(i));
}
peerNetwork.newPeers = new ArrayList<String>();
try
{
PrintWriter writePeerFile = new PrintWriter(new File("peers.lst"));
for (int i = 0; i < peers.size(); i++)
{
writePeerFile.println(peers.get(i));
}
writePeerFile.close();
} catch (Exception e)
{
System.out.println("[CRITICAL ERROR] UNABLE TO WRITE TO PEER FILE!");
e.printStackTrace();
}
}
//Look for new data from peers
for (int i = 0; i < peerNetwork.peerThreads.size(); i++)
{
ArrayList<String> input = peerNetwork.peerThreads.get(i).inputThread.readData();
if (input == null)
{
System.out.println("NULL RET RETRY");
System.exit(-4);
break;
}
/*
* While taking up new transactions and blocks, the client will broadcast them to the network if they are new to the client.
* As a result, if you are connected to 7 peers, you will get reverb 7 times for a broadcast of a block or transaction.
* For now, this is done to MAKE SURE everyone is on the same page with block/transaction propagation.
* In the future, much smarter algorithms for routing, perhaps sending "have you seen xxx transaction" or similar will be used.
* No point in sending 4 KB when a 64-byte message (or less) could check to make sure a transaction hasn't already been sent.
* Not wanting to complicate 2.0.0a5, there are no fancy algorithms or means of telling if peers have already heard the news you are going to deliver.
*/
for (int j = 0; j < input.size(); j++)
{
String data = input.get(j);
if (data.length() > 60)
{
System.out.println("got data: " + data.substring(0, 30) + "..." + data.substring(data.length() - 30, data.length()));
} else
{
System.out.println("got data: " + data);
}
String[] parts = data.split(" ");
if (parts.length > 0)
{
//NETWORK_STATE MAX_HEIGHT LAST_HASH
if (parts[0].equalsIgnoreCase("NETWORK_STATE"))
{
topBlock = Integer.parseInt(parts[1]);
}
else if (parts[0].equalsIgnoreCase("REQUEST_NET_STATE"))
{
peerNetwork.peerThreads.get(i).outputThread.write("NETWORK_STATE " + databaseMaster.getBlockchainLength() + " " + databaseMaster.getLatestBlock().blockHash);
for (int k = 0; k < pendingTransactionContainer.pendingTransactions.size(); k++)
{
peerNetwork.peerThreads.get(i).outputThread.write("TRANSACTION " + pendingTransactionContainer.pendingTransactions.get(k));
}
}
//BLOCK BLOCKDATA
else if (parts[0].equalsIgnoreCase("BLOCK"))
{
/*
* If a block is new to the client, the client will attempt to add it to the blockchain.
* When added to the blockchain, it may get added to a chain, put on a new fork, put on an existing, shorter-length chain that's forked less than 10 blocks back, or
* it may end up being queued or deleted. Queued blocks are blocks that self-validate (signatures match, etc.) but don't fit onto any chain.
* They are often used when getting blocks from a peer, in case one arrives out of order.
*/
System.out.println("Attempting to add block...");
boolean hasSeenBefore = false;
for (int k = 0; k < allBroadcastBlocks.size(); k++)
{
//Likely due to P2P reverb / echo
//System.out.println("Have seen block before... not adding.");
if (parts[1].equals(allBroadcastBlocks.get(k)))
{
hasSeenBefore = true;
}
}
if (!hasSeenBefore)
{
//Block has not been previously received, so it will be added to the blockchain (hopefully)
System.out.println("Adding new block from network!");
System.out.println("Block: ");
System.out.println(parts[1].substring(0, 30) + "...");
allBroadcastBlocks.add(parts[1]);
Block blockToAdd = new Block(parts[1]);
if (databaseMaster.addBlock(blockToAdd) && !catchupMode)
{
//If block is new to client and appears valid, rebroadcast
System.out.println("Added block " + blockToAdd.blockNum + " with hash: [" + blockToAdd.blockHash.substring(0, 30) + "..." + blockToAdd.blockHash.substring(blockToAdd.blockHash.length() - 30, blockToAdd.blockHash.length() - 1) + "]");
peerNetwork.broadcast("BLOCK " + parts[1]);
}
//Remove all transactions from the pendingTransactionPool that appear in the block
pendingTransactionContainer.removeTransactionsInBlock(parts[1]);
}
}
//TRANSACTION TRANSACTIONDATA
else if (parts[0].equalsIgnoreCase("TRANSACTION"))
{
/*
* Any transactions that are received will be checked against the table of existing received transactions. If they are new (and validate correctly), they will be added
* to the pending transaction pool. Currently, this pool is only useful when mining blocks. In the future, this pool will be accessible using RPC commands to show
* unconfirmed transactions, etc.
*/
boolean alreadyExisted = false;
for (int b = 0; b < allBroadcastTransactions.size(); b++)
{
if (parts[1].equals(allBroadcastTransactions.get(b)))
{
alreadyExisted = true;
}
}
if (!alreadyExisted) //Transaction was not already received
{
/*
* Put the transaction in the received transactions pile, check it for validity, and put it in the pool if valid.
* Important to note--validity checks are done by PendingTransactionContainer's addTransaction(String transaction) method.
* Also important to note--We add the transaction to the known transaction broadcast pile regardless of validity, to eliminate network reverb.
* Future versions will have better management of broadcast retention pools by checking for probable usefulness and not rebroadcasting
* known-useless transactions, such as those with indexes behind their current signature index, or ones that don't validate correctly.
*/
allBroadcastTransactions.add(parts[1]);
pendingTransactionContainer.addTransaction(parts[1]);
if (TransactionUtility.isTransactionValid(parts[1]))
{
System.out.println("New transaction on network:");
String[] transactionParts = parts[1].split(";");
for (int k = 2; k < transactionParts.length - 2; k+=2)
{
System.out.println(" " + transactionParts[k + 1] + " curecoin from " + transactionParts[0] + " to " + transactionParts[k]);
}
System.out.println("Total curecoin sent: " + transactionParts[1]);
peerNetwork.broadcast("TRANSACTION " + parts[1]);
}
else
{
System.out.println("Not a good transaction!");
//peerNetwork.broadcast("TRANSACTION " + parts[1]);
}
}
}
else if (parts[0].equalsIgnoreCase("PEER"))
{
/*
* Peer discovery mechanisms are currently limited.
*/
boolean exists = false;
for (int k = 0; k < peers.size(); k++)
{
if (peers.get(k).equals(parts[1] + ":" + parts[2]))
{
exists = true;
}
}
if (!exists)
{
try
{
peerNetwork.connectToPeer(parts[1].substring(0, parts[1].indexOf(":")), Integer.parseInt(parts[1].substring(parts[1].indexOf(":") + 1)));
peers.add(parts[1]);
PrintWriter out = new PrintWriter(peerFile);
for (int k = 0; k < peers.size(); k++)
{
out.println(peers.get(k));
}
out.close();
} catch (Exception e)
{
System.out.println("PEER COMMUNICATED INVALID PEER!");
}
}
}
else if (parts[0].equalsIgnoreCase("GET_PEER"))
{
/*
* Returns a random peer host/port combo to the querying peer.
* Future versions will detect dynamic ports and not send peers likely to not support direct connections.
* While not part of GET_PEER, very-far-in-the-future-versions may support TCP punchthrough assists.
*/
Random random = new Random();
peerNetwork.peerThreads.get(i).outputThread.write("PEER " + peers.get(random.nextInt(peers.size())));
}
else if (parts[0].equalsIgnoreCase("GET_BLOCK"))
{
try
{
Block block = databaseMaster.getBlock(Integer.parseInt(parts[1]));
if (block != null)
{
System.out.println("Sending block " + parts[1] + " to peer...");
peerNetwork.peerThreads.get(i).outputThread.write("BLOCK " + block.getRawBlock());
}
} catch (Exception e)
{
}
}
}
}
}
int currentChainHeight = databaseMaster.getBlockchainLength();
/*
* Current chain is shorter than peer chains. Chain starts counting at 0, so a chain height of 15, for example, means there are 15 blocks, and the top block's index is 14.
*/
if (topBlock > currentChainHeight)
{
catchupMode = true;
System.out.println("currentChainHeight: " + currentChainHeight);
System.out.println("topBlock: " + topBlock);
try
{
Thread.sleep(300); //Sleep for a bit, wait for responses before requesting more data.
} catch (Exception e)
{
//If this throws an error, something's terribly off.
System.out.println("MainClass has insomnia.");
}
for (int i = currentChainHeight; i < topBlock; i++) //Broadcast request for new block(s)
{
System.out.println("Requesting block " + i + "...");
peerNetwork.broadcast("GET_BLOCK " + i);
}
}
else
{
if (catchupMode)
{
System.out.println("Caught up with network."); //Probably won't be seen with block-add spam.
}
catchupMode = false;
}
/*
* Loop through all of the rpcAgent rpcThreads looking for new queries. Note that setting the response to a string twice in response to one command will cause queue issues.
* This may be changed in a later version, but I want to keep the RPCServer elements light on memory with less moving parts--they shouldn't be a point of failure.
* Keeping with only one String allowed in the output queue (instead of the ArrayList<String> model employed by the P2P networking functions) is simplistic for now.
*/
for (int i = 0; i < rpcAgent.rpcThreads.size(); i++)
{
String request = rpcAgent.rpcThreads.get(i).request;
if (request != null)
{
String[] parts = request.split(" ");
parts[0] = parts[0].toLowerCase();
if (parts[0].equals("getbalance"))
{
if (parts.length > 1)
{
rpcAgent.rpcThreads.get(i).response = databaseMaster.getAddressBalance(parts[1]) + ""; //Turn it into a String
}
else
{
rpcAgent.rpcThreads.get(i).response = databaseMaster.getAddressBalance(addressManager.getDefaultAddress()) + "";
}
}
else if (parts[0].equals("getinfo"))
{
/*
* getinfo will be expanded in the future to give a lot more information.
* This is the bare minimum required to function for debugging.
*/
String response = "Blocks: " + databaseMaster.getBlockchainLength();
response += "\nLast block hash: " + databaseMaster.getBlock(databaseMaster.getBlockchainLength() - 1).blockHash;
response += "\nDifficulty: " + databaseMaster.getDifficulty();
response += "\nMain address: " + addressManager.getDefaultAddress();
response += "\nMain address balance: " + databaseMaster.getAddressBalance(addressManager.getDefaultAddress());
rpcAgent.rpcThreads.get(i).response = response;
}
else if (parts[0].equals("send"))
{
try
{
long amount = Long.parseLong(parts[1]);
String destinationAddress = parts[2];
String address = addressManager.getDefaultAddress();
String fullTransaction = addressManager.getSignedTransaction(destinationAddress, amount, databaseMaster.getAddressSignatureIndex(address) + addressManager.getDefaultAddressIndexOffset());
addressManager.incrementDefaultAddressIndexOffset();
System.out.println("Attempting to verify transaction... " + TransactionUtility.isTransactionValid(fullTransaction));
if (TransactionUtility.isTransactionValid(fullTransaction))
{
pendingTransactionContainer.addTransaction(fullTransaction);
peerNetwork.broadcast("TRANSACTION " + fullTransaction);
System.out.println("Sending " + amount + " from " + address + " to " + destinationAddress);
rpcAgent.rpcThreads.get(i).response = "Sent " + amount + " from " + address + " to " + destinationAddress;
}
else
{
rpcAgent.rpcThreads.get(i).response = "UNABLE TO SEND: Invalid transaction!";
}
} catch (Exception e)
{
rpcAgent.rpcThreads.get(i).response = "Syntax (don't use < and >): send <amount> <destination>";
}
}
else if (parts[0].equals("submittx"))
{
if (TransactionUtility.isTransactionValid(parts[1]))
{
pendingTransactionContainer.addTransaction(parts[0]);
peerNetwork.broadcast("TRANSACTION " + parts[1]);
rpcAgent.rpcThreads.get(i).response = "Sent raw transaction!";
}
else
{
rpcAgent.rpcThreads.get(i).response = "Non-valid transaction.";
}
}
else if (parts[0].equals("trypos"))
{
rpcAgent.rpcThreads.get(i).request = null;
/*
* In order to attempt a PoS block, we need to check a few conditions. These will, of course,
* also be verified by the network in order to ensure the PoS block is valid.
*
* Conditions:
* 1.) Current address hasn't mined any PoS blocks in the last 50 blocks
* 2.) Current address hasn't sent any transactions in the last 50 blocks
*
* The number of coins CURRENTLY in the address acts as the number of possible "nonces" to try.
* As such, this version of PoS could be abused by sending coins to the address right before it stakes.
* Since this is a tech demo of a possible PoS model on testnet, this isn't important, but the final
* version of PoS will only consider the coins that were in the address before the previous 50 blocks.
*
* One issue that comes up when only part of the blockchain is stored is being unable to verify this
* initial balance. As such, a 'thin client' would simply have to trust that, when receiving a block
* during catchup mode (as all clients will store enough blocks to verify CURRENT PoS blocks, aka ones mined
* while the client is running) will simply have to trust that the rest of the network properly verified
* this balance. Full nodes will be able to perform complete validation routines. In effect, full nodes
* will maintain some sanity, but given the nature of jigsaw-blockchains where each block fits ONLY on the
* previous, in order for a PoS block to be rejected in catchup mode, the entire blockchain after that point
* would have to be invalidated.
*
* In the current version, the submitted balance is simply trusted if the client is in catchup mode.
*/
// Address can not have mined a PoS block or sent a transaction in the last 50 blocks
String PoSAddress = addressManager.getDefaultAddress();
boolean conditionsMet = true;
for (int j = databaseMaster.getBlockchainLength() - 1; j > databaseMaster.getBlockchainLength() - 50; j--)
{
if (!databaseMaster.getBlock(j).isPoWBlock()) // Then PoS block
{
if (databaseMaster.getBlock(j).getMiner().equals(PoSAddress))
{
// Address has mined PoS block too recently!
rpcAgent.rpcThreads.get(i).response = "A PoS block was mined too recently: " + j;
conditionsMet = false;
}
}
ArrayList<String> transactions = databaseMaster.getBlock(i).getTransactionsInvolvingAddress(PoSAddress);
for (String transaction : transactions)
{
if (transaction.split(":")[0].equals(PoSAddress))
{
// Address has sent coins too recently!
rpcAgent.rpcThreads.get(i).response = "A PoS block was mined too recently: " + j;
conditionsMet = false;
}
}
}
if (conditionsMet)
{
System.out.println("Last block: " + databaseMaster.getBlockchainLength());
System.out.println("That block's hash: " + databaseMaster.getBlock(databaseMaster.getBlockchainLength() - 1).blockHash);
String previousBlockHash = databaseMaster.getBlock(databaseMaster.getBlockchainLength() - 1).blockHash;
long currentBalance = databaseMaster.getAddressBalance(PoSAddress);
Certificate certificate = new Certificate(PoSAddress, "0", (int)currentBalance * 100, "0", databaseMaster.getBlockchainLength() + 1, previousBlockHash, 0, "0,0");
String[] scoreAndNonce = certificate.getMinCertificateScoreWithNonce().split(":");
int bestNonce = Integer.parseInt(scoreAndNonce[0]);
long lowestScore = Long.parseLong(scoreAndNonce[1]);
long target = Long.MAX_VALUE/(100000/2); // Hard-coded PoS difficulty for this test
if (lowestScore < target)
{
try //Some stuff here may throw exceptions
{
//Great, certificate is a winning certificate!
//Gather all of the transactions from pendingTransactionContainer, check them.
ArrayList<String> allPendingTransactions = pendingTransactionContainer.pendingTransactions;
System.out.println("Inital pending pool size: " + allPendingTransactions.size());
allPendingTransactions = TransactionUtility.sortTransactionsBySignatureIndex(allPendingTransactions);
System.out.println("Pending pool size after sorting: " + allPendingTransactions.size());
//All transactions have been ordered, and tested for validity. Now, we need to check account balances to make sure transactions are valid.
//As all transactions are grouped by address, we'll check totals address-by-address
ArrayList<String> finalTransactionList = new ArrayList<String>();
for (int j = 0; j < allPendingTransactions.size(); j++)
{
String transaction = allPendingTransactions.get(j);
String address = transaction.split(";")[0];
//Begin at 0L, and add all outputs to exitBalance
long exitBalance = 0L;
long originalBalance = databaseMaster.getAddressBalance(address);
//Used to keep track of the offset from j while still working on the same address, therefore not going through the entire for-loop again
int counter = 0;
//Previous signature count for an address--in order to ensure transactions use the correct indices
long previousSignatureCount = databaseMaster.getAddressSignatureIndex(address);
boolean foundNewAddress = false;
while (!foundNewAddress && j + counter < allPendingTransactions.size())
{
transaction = allPendingTransactions.get(j + counter);
if (!address.equals(transaction.split(";")[0]))
{
foundNewAddress = true;
address = transaction.split(";")[0];
j = j + counter;
}
else
{
exitBalance += Long.parseLong(transaction.split(";")[1]); //Element at index 1 (2nd element) is the full output amount!
if (exitBalance <= originalBalance && previousSignatureCount + 1 == Long.parseLong(transaction.split(";")[transaction.split(";").length - 1])) //Transaction looks good!
{
//Add seemingly-good transaction to the list, and increment previousSignatureCount for signature order assurance.
finalTransactionList.add(transaction);
System.out.println("While making block, added transaction " + transaction);
previousSignatureCount++;
}
else
{
System.out.println("Transaction failed final validation...");
System.out.println("exitBalance: " + exitBalance);
System.out.println("originalBalance: " + originalBalance);
System.out.println("previousSignatureCount: " + previousSignatureCount);
System.out.println("signature count of new tx: " + Long.parseLong(transaction.split(";")[transaction.split(";").length - 1]));
}
//Counter keeps track of the sub-2nd-layer-for-loop incrementation along the ArrayList. It's kinda 3D.
counter++;
}
}
}
//We have the transaction list; now we need to assemble the block. I moved this code into its own method, because it would be ugly here. That method handles steps 5, 6, and 7.
//databaseMaster.getBlockchainLength() doesn't have one added to it to account for starting from 0!
String fullBlock = BlockGenerator.compileBlock(System.currentTimeMillis(), databaseMaster.getBlockchainLength(), databaseMaster.getLatestBlock().blockHash, 100000 /*fixed testnet PoS difficulty for now...*/, bestNonce, "0000000000000000000000000000000000000000000000000000000000000000", finalTransactionList, certificate, certificate.redeemAddress, addressManager.getDefaultPrivateKey(), databaseMaster.getAddressSignatureIndex(certificate.redeemAddress));
System.out.println("Compiled PoS block: " + fullBlock);
//We finally have the full block. Now to submit it to ourselves...
Block toAdd = new Block(fullBlock);
boolean success = databaseMaster.addBlock(toAdd);
System.out.println("Block add success: " + success);
if (success) //The block appears legitimate to ourselves! Send it to others!
{
peerNetwork.broadcast("BLOCK " + fullBlock);
System.out.println("PoS Block added to network successfully!");
pendingTransactionContainer.reset(); //Any transactions left in pendingTransactionContainer that didn't get submitted into the block should be cleared anyway--they probably aren't valid for some reason, likely balance issues.
addressManager.resetDefaultAddressIndexOffset();
}
else
{
System.out.println("Block was not added successfully! :(");
}
rpcAgent.rpcThreads.get(i).response = "Successfully submitted block! \nCertificate earned score " + lowestScore + "\nWhich is below target " + target + " so earned PoS!";
} catch (Exception e)
{
rpcAgent.rpcThreads.get(i).response = "Failure to construct certificate!";
System.out.println("Constructing certificate failed!");
e.printStackTrace();
}
}
else
{
rpcAgent.rpcThreads.get(i).response = "Pos mining failed with target score " + lowestScore + "\nWhich is above target " + target;
}
}
}
else if (parts[0].equals("submitcert"))
{
rpcAgent.rpcThreads.get(i).request = null;
/*
* We have seven things to do:
* 1.) Check certificate for all nonces
* If 1. shows a difficulty above the network difficulty (below the target), proceed with creating a block:
* 2.) Gather all transactions from the pending transaction pool. Test all for validity. Test all under a max balance test.
* 3.) Put correct transactions in any arbitrary order, except for multiple transactions from the same address, which are ordered by signature index.
* 4.) Input the ledger hash (In 2.0.0a5, this is 0000000000000000000000000000000000000000000000000000000000000000, as ledger hashing isn't fully implemented)
* 5.) Hash the block
* 6.) Sign the block
* 7.) Return full block
* Steps 5, 6, and 7 are handled outside of MainClass, by a static method inside BlockGenerator.
*/
//First, we'll check for the max difficulty.
Certificate certificate = new Certificate(parts[1]);
String[] scoreAndNonce = certificate.getMinCertificateScoreWithNonce().split(":");
int bestNonce = Integer.parseInt(scoreAndNonce[0]);
long lowestScore = Long.parseLong(scoreAndNonce[1]);
long target = Long.MAX_VALUE/(databaseMaster.getDifficulty()/2); //Difficulty and target have an inverse relationship.
if (lowestScore < target)
{
try //Some stuff here may throw exceptions
{
//Great, certificate is a winning certificate!
//Gather all of the transactions from pendingTransactionContainer, check them.
ArrayList<String> allPendingTransactions = pendingTransactionContainer.pendingTransactions;
System.out.println("Inital pending pool size: " + allPendingTransactions.size());
allPendingTransactions = TransactionUtility.sortTransactionsBySignatureIndex(allPendingTransactions);
System.out.println("Pending pool size after sorting: " + allPendingTransactions.size());
//All transactions have been ordered, and tested for validity. Now, we need to check account balances to make sure transactions are valid.
//As all transactions are grouped by address, we'll check totals address-by-address
ArrayList<String> finalTransactionList = new ArrayList<String>();
for (int j = 0; j < allPendingTransactions.size(); j++)
{
String transaction = allPendingTransactions.get(j);
String address = transaction.split(";")[0];
//Begin at 0L, and add all outputs to exitBalance
long exitBalance = 0L;
long originalBalance = databaseMaster.getAddressBalance(address);
//Used to keep track of the offset from j while still working on the same address, therefore not going through the entire for-loop again
int counter = 0;
//Previous signature count for an address--in order to ensure transactions use the correct indices
long previousSignatureCount = databaseMaster.getAddressSignatureIndex(address);
boolean foundNewAddress = false;
while (!foundNewAddress && j + counter < allPendingTransactions.size())
{
transaction = allPendingTransactions.get(j + counter);
if (!address.equals(transaction.split(";")[0]))
{
foundNewAddress = true;
address = transaction.split(";")[0];
j = j + counter;
}
else
{
exitBalance += Long.parseLong(transaction.split(";")[1]); //Element at index 1 (2nd element) is the full output amount!
if (exitBalance <= originalBalance && previousSignatureCount + 1 == Long.parseLong(transaction.split(";")[transaction.split(";").length - 1])) //Transaction looks good!
{
//Add seemingly-good transaction to the list, and increment previousSignatureCount for signature order assurance.
finalTransactionList.add(transaction);
System.out.println("While making block, added transaction " + transaction);
previousSignatureCount++;
}
else
{
System.out.println("Transaction failed final validation...");
System.out.println("exitBalance: " + exitBalance);
System.out.println("originalBalance: " + originalBalance);
System.out.println("previousSignatureCount: " + previousSignatureCount);
System.out.println("signature count of new tx: " + Long.parseLong(transaction.split(";")[transaction.split(";").length - 1]));
}
//Counter keeps track of the sub-2nd-layer-for-loop incrementation along the ArrayList. It's kinda 3D.
counter++;
}
}
}
//We have the transaction list; now we need to assemble the block. I moved this code into its own method, because it would be ugly here. That method handles steps 5, 6, and 7.
//databaseMaster.getBlockchainLength() doesn't have one added to it to account for starting from 0!
String fullBlock = BlockGenerator.compileBlock(System.currentTimeMillis(), databaseMaster.getBlockchainLength(), databaseMaster.getLatestBlock().blockHash, 150000, bestNonce, "0000000000000000000000000000000000000000000000000000000000000000", finalTransactionList, certificate, certificate.redeemAddress, addressManager.getDefaultPrivateKey(), databaseMaster.getAddressSignatureIndex(certificate.redeemAddress));
//We finally have the full block. Now to submit it to ourselves...
Block toAdd = new Block(fullBlock);
boolean success = databaseMaster.addBlock(toAdd);
if (success) //The block appears legitimate to ourselves! Send it to others!
{
System.out.println("Block added to network successfully!");
peerNetwork.broadcast("BLOCK " + fullBlock);
pendingTransactionContainer.reset(); //Any transactions left in pendingTransactionContainer that didn't get submitted into the block should be cleared anyway--they probably aren't valid for some reason, likely balance issues.
addressManager.resetDefaultAddressIndexOffset();
}
else
{
System.out.println("Block was not added successfully! :(");
}
rpcAgent.rpcThreads.get(i).response = "Successfully submitted block! \nCertificate earned target score " + lowestScore + "\nWhich is below target " + target;
} catch (Exception e)
{
rpcAgent.rpcThreads.get(i).response = "Failure to construct certificate!";
System.out.println("Constructing certificate failed!");
e.printStackTrace();
}
}
else
{
rpcAgent.rpcThreads.get(i).response = "Certificate failed with target score " + lowestScore + "\nWhich is above target " + target;
}
}
else if (parts[0].equals("gethistory"))
{
if (parts.length > 1)
{
ArrayList<String> allTransactions = databaseMaster.getAllTransactionsInvolvingAddress(parts[1]);
String allTransactionsFlat = "";
for (int j = 0; j < allTransactions.size(); j++)
{
allTransactionsFlat += allTransactions.get(j) + "\n";
}
rpcAgent.rpcThreads.get(i).response = allTransactionsFlat;
}
else
{
rpcAgent.rpcThreads.get(i).response = "gethistory <address>";
}
}
else if (parts[0].equals("getpending"))
{
if (parts.length > 1)
{
rpcAgent.rpcThreads.get(i).response = "" + pendingTransactionContainer.getPendingBalance(parts[1]);
}
else
{
rpcAgent.rpcThreads.get(i).response = "getpending <address>";
}
}
else
{
rpcAgent.rpcThreads.get(i).response = "Unknown command \"" + parts[0] + "\"";
}
}
}
try
{
Thread.sleep(100);
} catch (Exception e) {}
}
}
public static void launch()
{
System.out.println("Launching...");
Console console = System.console(); //Get a system console object
if (console != null) //If the application has a console
{
File f = new File("launch.bat");
if (f.exists())
{
f.delete(); //delete bat file if it exists
}
}
else if (!GraphicsEnvironment.isHeadless()) //Application doesn't have a console, let's give it one!
{
System.out.println("Giving console");
String os = System.getProperty("os.name").toLowerCase(); //Get OS
if (os.contains("indows")) //If OS is a windows OS
{
try
{
File JarFile = new File(MainClass.class.getProtectionDomain().getCodeSource().getLocation().toURI());//Get the absolute location of the .jar file
PrintWriter out = new PrintWriter(new File("launch.bat")); //Get a PrintWriter object to make a batch file
out.println("@echo off"); //turn echo off for batch file
out.println("title Curecoin 2.0.0a5");
out.println("java -Xmx500M -jar \"" + JarFile.getPath() + "\"");
out.println("start /b \"\" cmd /c del \"%~f0\"&exit /b");
out.close(); //saves file
Runtime rt = Runtime.getRuntime(); //gets runtime
rt.exec("cmd /c start launch.bat"); //executes batch file
} catch (Exception e)
{
e.printStackTrace();
}
System.exit(0); //Exit program, so only instance of program with command line runs!
}
}
}
}