-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNoob.java
More file actions
257 lines (196 loc) · 10.5 KB
/
Noob.java
File metadata and controls
257 lines (196 loc) · 10.5 KB
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
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.security.Security;
import java.util.HashMap;
import javax.swing.SwingUtilities;
import java.security.PublicKey;
public class Noob {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static HashMap<String, TransactionOutput> UTXOs = new HashMap<String, TransactionOutput>(); //list of all unspent transactions.
public static ArrayList<Transaction> mempool = new ArrayList<Transaction>(); // we are using mempool for unconfirmed transaction
public static int difficulty = 3;
public static long minimumTransaction = 10_00000000L;
public static long miningReward = 1_000_000L;
public static int retargetInterval = 3; // recalculate difficulty every 3 blocks
public static long targetBlockTimeMs = 2000; // target block time in miliseconds
public static Wallet walletA;
public static Wallet walletB;
public static Transaction genesisTransaction;
public static void main(String[] args) {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// Create the new wallets
walletA = new Wallet();
walletB = new Wallet();
Wallet coinbase = new Wallet();
System.out.println("Coinbase key:" + coinbase.publicKey);
System.out.println("walletA key: " + walletA.publicKey);
// Create genesis transaction, which sends 100 NoobCoin to walletA;
genesisTransaction = new Transaction(coinbase.publicKey, walletA.publicKey,100_00000000L , 0L,null);
genesisTransaction.generateSignature(coinbase.privateKey); // manually sign the genesis transaction
genesisTransaction.transactionId = "0"; // manually set the transaction id
genesisTransaction.outputs.add(new TransactionOutput(genesisTransaction.recipient, genesisTransaction.value, genesisTransaction.transactionId)); // manually add the Transactions Output
UTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
System.out.println("Creating and Mining Genesis block... ");
Block genesis = new Block("0");
genesis.addTransaction(genesisTransaction);
addBlock(genesis);
SwingUtilities.invokeLater(Dashboard::new);
// WalletA is miner now they will earn rewards for mining the block
System.out.println("\n WalletA balance: " + StringUtil.toCoins( walletA.getBalance()));
System.out.println("\n WalletA send 40 to walletB..");
walletA.sendFunds(walletB.publicKey, 40_00000000L,50000000L);
// WalletA mines earn 10 coin reward automatically
System.out.println("\nWalletA mines block 1 earn reward..");
Block block1 = mineNextBlock(genesis.hash, walletA);
System.out.println("\n WalletA balance: " + StringUtil.toCoins(walletA.getBalance()));
System.out.println("\n WalletB balance: " + StringUtil.toCoins(walletB.getBalance()));
System.out.println("\nWalletB send 20 to walletA..");
walletB.sendFunds(walletA.publicKey, 20_00000000L, 50000000L);
// walletB mines block 2 earn 10 coins
System.out.println("\nWalletB mines block 2 earn reward..");
Block block2 = mineNextBlock(block1.hash, walletB);
System.out.println("\nWalletA balance: " + StringUtil.toCoins(walletA.getBalance()));
System.out.println("\nWalletB balance: " + StringUtil.toCoins(walletB.getBalance()));
System.out.println("\nWalletA send 40 to walletB");
walletA.sendFunds(walletB.publicKey, 40_00000000L, 50000000L);
Block b1 = mineNextBlock(blockchain.get(blockchain.size() - 1).hash, walletA);
System.out.println("\nWalletB send 10 to walletA");
walletB.sendFunds(walletB.publicKey,10_00000000L, 50000000L);
Block b2 = mineNextBlock(blockchain.get(blockchain.size() - 1).hash, walletB);
System.out.println("Final balance");
System.out.println("\nWalletA balance: " + StringUtil.toCoins(walletA.getBalance()));
System.out.println("\nWalletB balance: " + StringUtil.toCoins(walletB.getBalance()));
isChainValid();
}
// Creating a new coinbase transaction no inputs creating coins from nothing and sending to miner
public static Transaction createCoinbaseTx(Wallet miner,long totalReward){
Transaction coinbaseTx = new Transaction(null,miner.publicKey, totalReward,0L, new ArrayList<>());
coinbaseTx.transactionId = StringUtil.applySha256(
"COINBASE"+ miner.publicKey.toString() + System.currentTimeMillis()
);
TransactionOutput out = new TransactionOutput(
miner.publicKey,
miningReward,
coinbaseTx.transactionId
);
coinbaseTx.outputs.add(out);
UTXOs.put(out.id,out);
System.out.println("Coinbase reward: " + miningReward + "coins> miner");
return coinbaseTx;
}
// Miner drains mempool into new block and mines it
public static Block mineNextBlock(String previousHash, Wallet miner) {
Block block = new Block(previousHash);
// Step1: Sort mempool by fee descending-highest fee minded first
mempool.sort((a,b) -> Long.compare(b.fee,a.fee));
// Step2: Sum all fee from pending transaction
long totalFees = mempool.stream().mapToLong(tx -> tx.fee).sum();
// Step3: Coinbase = block reward + all fees collected
long totalReward = miningReward + totalFees;
Transaction coinbase = createCoinbaseTx(miner,totalReward);
block.addCoinbase(coinbase);
if (mempool.isEmpty()) {
System.out.println("Mempool is empty nothing to mine");
return null;
}
// pulling every pending transaction into this block
for (Transaction tx : mempool) {
block.addTransaction(tx);
}
mempool.clear(); // Empty the waiting room
addBlock(block);
System.out.println("Block mined with " + block.transactions.size() + " transactions(s)");
return block;
}
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
HashMap<String, TransactionOutput> tempUTXOs = new HashMap<String, TransactionOutput>();
tempUTXOs.put(genesisTransaction.outputs.get(0).id, genesisTransaction.outputs.get(0));
//loop through blockchain to check hashes:
for (int i = 1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i - 1);
//compare registered hash and calculated hash:
if (!currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("Current Hashes not equal");
return false;
}
//compare previous hash and registered previous hash
if (!previousBlock.hash.equals(currentBlock.previousHash)) {
System.out.println("Previous Hashes not equal");
return false;
}
String hashTarget = new String(new char[currentBlock.difficulty]).replace('\0', '0');
if (!currentBlock.hash.substring(0,currentBlock.difficulty).equals(hashTarget)) {
System.out.println("This block hasn't been mined");
return false;
}
TransactionOutput tempOutput;
for (int t = 0; t < currentBlock.transactions.size(); t++) {
Transaction tx = currentBlock.transactions.get(t);
if(t == 0 && tx.sender == null){
for(TransactionOutput output: tx.outputs){
tempUTXOs.put(output.id, output);
}
continue;
}
if (!tx.verifySignature()) {
System.out.println("Signature on Transaction(" + t + ") is Invalid");
return false;
}
if (tx.getInputsValue() != tx.getOutputsValue()+tx.fee) {
System.out.println("Inputs are not equal to outputs on Transaction(" + t + ")");
return false;
}
for (TransactionInput input : tx.inputs) {
tempOutput = tempUTXOs.get(input.transactionOutputId);
if (tempOutput == null) {
System.out.println("Referenced input on Transaction(" + t + ") is Missing");
return false;
}
if (input.UTXO.value != tempOutput.value) {
System.out.println("Referenced input Transaction(" + t + ") value is Invalid");
return false;
}
tempUTXOs.remove(input.transactionOutputId);
}
for (TransactionOutput output : tx.outputs) {
tempUTXOs.put(output.id, output);
}
if (tx.outputs.get(0).recipient != tx.recipient) {
System.out.println("Transaction(" + t + ") output recipient is not who it should be");
return false;
}
}
}
System.out.println("Blockchain is valid");
return true;
}
private static void addBlock(Block newBlock) {
newBlock.mineBlock(difficulty);
blockchain.add(newBlock);
adjustDifficulty();
}
public static void adjustDifficulty(){
int chainSize = blockchain.size();
if(chainSize == 0 || chainSize % retargetInterval != 0) return; // Only retarget at interval Boundary
Block latest = blockchain.get(chainSize - 1);
Block reference = blockchain.get(chainSize - retargetInterval);
long actualTimeMs = latest.timeStamp - reference.timeStamp;
long expectedTimeMs = targetBlockTimeMs * retargetInterval;
System.out.println("Expected time: " + expectedTimeMs + "ms");
System.out.println("Actual time: " + actualTimeMs + "ms");
if(actualTimeMs < expectedTimeMs /2){
// mining too fast increase difficulty
difficulty++;
System.out.println("Blocked mined too fast > diffulcty increase to: "+ difficulty);
} else if(actualTimeMs > expectedTimeMs * 2){
//Mining too slow decraese difficulty
difficulty = Math.max(1, difficulty -1);
System.out.println("Blocked mined too slow > diffulcty decrease to: "+ difficulty);
} else{
System.out.println("Blocked mined within acceptable time > diffulcty stays the same"+ difficulty);
}
}
}