Commit b2f1f374 authored by Michel Schudel's avatar Michel Schudel

more refactoring

parent 6b56fe1b
# CraftsCoin workshop
In deze workshop gaan we zelf een blockhain bouwen. Het smart contract dat we gaan maken heet CraftsCoin, een cryptomunt die we binnen Craftsmen kunnen gebruiken om etentjes, declaraties en dergelijke kunnen betalen.
Stap voor stap gaan we de blockchain opbouwen. We gaan de volgende functionaliteit implementeren:
- Het SmartContract, in ons geval een simpele transactie
- De blockchain waar onze contracten in opgeslagen worden
- Mining; oftewel een nieuw block bouwen
- Validatie, controleren of de blockchain nog in orde is
- Aansluiting op het peer-to-peer netwerk (je collega's!) zodat we samen coins kunnen uitwisselen
## Requirements
- Java 8
- Intellij / Eclipse IDE
- NodeJS (versie 8.9.4+). Zorg ervoor dat je tenminste deze versie geinstalleerd hebt.
## Initiele build
Nadat je het project uit github hebt uitgecheckt, dien je het project eenmalig te bouwen:
```
gradlew build
```
Dit duurt ongeveer 2 minuten. Je kan nu het project starten:
```
gradlew bootRun
```
Dit start een SpringBoot applicatie op poort 8080 op. Ga naar http://localhost:8080/index.html om de UI te zien. Hier zie je de volgende tabs:
1. *Pending transactions*. Hier zie je alle transacties die nog niet in de blockchain opgenomen zijn.
2. *New transaction*. Hier kan je een nieuwe transactie het netwerk inschieten.
3. B*lockchain*. Hier zie je de huidige blockchain.
4. *Peers*. Hier staat een overzicht welke peers er allemaal aan deze node verbonden zijn.
5. *Mine*. Hier kan je een mining actie starten.
6. *Wallet*. Hier kan je een wallet bekijken.
Dit correspondeert met de volgende endpoints:
```
GET /api/pendingtransactions
POST /api/newtransaction
GET /api/blockchain
GET /api/wallet/{walletId}
GET /api/peers
POST /api/mine
```
Uiteraard kan je middels Postman deze api's ook benaderen. De Frontend is dus alleen ter convenience.
## Bouwen van het block
Maak een `Block` class met de volgende inhoud:
- `index` (long). Dit is de zogenaamde BlockHeight en geeft aan hoeveel blocks het block van het genesis block verwijderd is.
- `timestamp` (long)
- lijst van transacties.
- `previousHash` (string). De hash van het vorige block. dit geeft de blockchain immutability.
- `proof` (long). Het resultaat van een mining actie, hierover later meer.
## De initiele blockchain
Maak een `Blockchain` class met als private field een lijst van `Block`.
Als we starten hebben we natuurlijk nog geen blocks. Daarom is het zaak om bij het starten van de applicatie een eerste block te maken, het zogenaamde *genesis* block.
Maak hiervoor een static factory method `create` op de blockchain class. Deze method creert een nieuwe blockchain met een eerste block. De vulling van dit block is als volgt:
- `index`: 0
- `timestamp`: (nu)
- geen transacties
- `previousHash`: 0. Er is namelijk geen previous block.
- `proof`: arbitrary number, bv 100. Dit block is niet gemined.
Maak nu een `BlockChainManager` SpringBean met een `BlockChain` field en een `init` method (met `@PostConstruct`) die het blockchain field vult met een initiele blockchain. Sluit in de restcontroller het blockchain endpoint aan op de BlockchainManager.
Test middels de UI of PostMan of je een initiele blockchain te zien krijgt.
## Het smart contract
Voordat we ons eerste block kunnen maken, hebben we transacties nodig die in het block terechtkomen. Dat gaan we nu doen.
Ons smart contract bestaat uit een simpele coin transactie. Maak een `Transaction` class, als volgt:
- Velden: `transactionId` (UUID), `from` (String), `to`(String), `amount` (BigDecimal)
- equals en hashcode contract *enkel* op basis van transactionId
- respresentatieve `toString()` implementatie (bv middels code generatie in IntelliJ)
## De transaction pool
We hebben een plaats nodig waar we onze transacties tijdelijk op kunnen slaan zodat we deze transacties kunnen toevoegen. Dit is vergelijkbaar met de MemPool in BitCoin. Maak een SpringBean `TransactionPool` class die aan de volgende kenmerken voldoet:
- `addTransaction` voegt een nieuwe transactie toe aan de poel
- `getAllTransactions` levert een lijst van alle transacties op
In de CraftsCoinRestController staan de functies `newTransaction` en `pendingTransactions`. Sluit deze aan op `addTransaction` resp. `getAllTransactions`.
Test middels de frontend of PostMan of beide REST services naar behoren werken.
## Mining van een nieuw block
Nu we de transaction pool werkend hebben, wordt het tijd om een nieuw block te minen! Zoals we hebben gezien, vergt het minen van een block enige moeite, maar in ons model levert het ook gratis geld op, namelijk 10 gloednieuwe CraftCoins!
### Proof of work
Als eerste dienen we aan te tonen dat we het mining werk gedaan hebben middels een nieuw zg. proof of work. Dit proof dient lastig te zijn om te creeren, maar gemakkelijk om te valideren. Andere nodes in het netwerk dienen namelijk te kunnen controleren of een block valide is.
Het proof of work moet als volgt berekend worden:
- Neem het proof of work van het meest recente block in de blockchain;
- Creer een nieuw proof of work: 0.
- Plak het oude en het nieuwe proof aan elkaar (String concat)
- Maak een hash van de string middels `HashUtils.createHash(string)`
- Begint de hash met '0000'? Dan is het nieuwe proof of work geldig.
- Begint de hash niet met '0000'? Hoog dan het nieuwe proof of work op met 1 en hash opnieuw.
BitCoin gebruikt het HashCash algoritme, wat erg lijkt op bovenstaande algoritme. De eindconditie is alleen veel strenger, waardoor het lang duurt voordat een nieuw proof of work gevonden is.
### Een nieuw block aanmaken.
Nu we het nieuwe proof of work hebben gevonden, is het tijd om een nieuw blok te maken. Maak het nieuwe blok als volgt:
- `index` (long) -> index van het meest recente block + 1.
- `timestamp` (long) -> nu
- lijst van alle transacties in de transactie pool, plus een reward transactie:
- `from`: null of ""
- `to`: jezelf natuurlijk! Vul hier je naam in.
- `amount`: 10
- `previousHash` (string). De hash van het meest recente block. De hash mag je berekenen over de string representatie van het meest recente block.
- `proof` (long). Het proof of work dat je in de vorige stap hebt berekend.
Voeg dit block toe aan het einde van de chain.
Voeg het hele mining gebeuren nu samen in een functie mine() die je middels de restcontroller van buiten kan aanroepen. Test met de UI of Postman of het werkt.
Je hebt nu een volledig werkende Blockchain node die een blockchain heeft, een transactie pool, en mining functionaliteit!
## Persistentie
Het is handig om de blockchain naar disk weg te schrijven. Gebruik daarvoor de functies `loadBlockchain` en `saveBlockchain` in de `BlockchainRepository` class:
- Probeer de blockchain te laden alvorens je een genesis block aanmaakt. Lukt dit niet, intialiseer de blockchain dan alsnog (met een genesis block). Sla de blockchain daarna meteen op.
- Sla de blockchain ook op nadat je een nieuw block aan de blockchain hebt toegevoegd.
- Test weer middels de UI of Postman of de blockchain bewaard blijft (als json file in de root van het project).
## Een gedistribueerde blockchain
Een Blockchain op 1 node heeft natuurlijk weinig zin. Daarom gaan we nu de blockchain aansluiten op een peer-to-peer netwerk. In eerste instantie met meerdere nodes op je eigen laptop, maar het zou natuurlijk leuk zijn als we ook verbinding kunnen maken met nodes op machines van je eigen collega's!
## Bonus: wallet
Een wallet is niets anders dan een optelsom van alle transacties waar jij als gebruiker ooit bij betrokken bent geweest. Schrijf een wallet functie die een balans opmaakt van alle verzonden en ontvangen coins op jouw naam (interessante vraag: moet je hier niet-bevestigde transacties, die nog in de pool staan, ook meenemen?).
/* transactionsComponent's private CSS styles */
.selected {
background-color: #CFD8DC !important;
color: white;
}
.transactions {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
}
.transactions tr {
background-color: #EEE;
}
.transactions th {
background-color: #555;
}
.transactions tr.selected:hover {
background-color: #BBD8DC !important;
color: white;
}
.transactions tr:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.transactions .text {
position: relative;
top: -3px;
}
.transactions .badge {
font-size: small;
color: white;
}
<h2>Pending transactions</h2>
<table class="transactions">
<tr>
<th>transaction id</th>
<th>from</th>
<th>to</th>
<th>amount</th>
</tr>
<tr *ngFor="let transaction of transactions"
[class.selected]="transaction === selectedTransaction"
(click)="onSelect(transaction)">
<td>{{transaction.id}}</td>
<td>{{transaction.from}}</td>
<td>{{transaction.to}}</td>
<td>{{transaction.amount}}</td>
</tr>
</table>
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TransactionsComponent } from './transactions.component';
describe('TransactionsComponent', () => {
let component: TransactionsComponent;
let fixture: ComponentFixture<TransactionsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TransactionsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TransactionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import {Transaction} from '../transaction';
import { TransactionService } from '../transaction.service';
@Component({
selector: 'app-transactions',
templateUrl: './transactions.component.html',
styleUrls: ['./transactions.component.css']
})
export class TransactionsComponent implements OnInit {
selectedTransaction: Transaction
transactions: Transaction[];
constructor(private transactionService: TransactionService) { }
ngOnInit() {
this.getTransactions();
}
onSelect(transaction: Transaction): void {
this.selectedTransaction = transaction;
}
getTransactions(): void {
this.transactionService.getTransactions()
.subscribe(transactions => this.transactions = transactions);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment