Wollte heute mal noch vor dem Börsengang von Coinbase ein Blockchain in Java coden. Mit nur zwei Klassen sind die Grundlagen implementiert. D.h. ein Block verweist jeweils auf den nächsten Block. Jeder Block hat einen Hash über die Daten und kann so von jedem validiert werden. Ein Block kann alles enthalten, hier mal ein String Objekt, es muss ja nicht immer Bitcoin sein. Hier die Architektur:
Ein JUnit 5 Testklasse erzeugt eine gültige Blockchain mit 5 Blöcken und wird anschließend validiert. Bei Bitcoin werden alle 10 Minuten neue Blöcke erzeugt. Die haben dann jeweils (ursprünglich) eine Größe von ca. max 1 MB an Daten.
Dann im zweiten Testfall wird ein Block verändert und in die Blockhain eingefügt. Das Ergebnis der validierung muss dann einen Error ausgeben.
Ein Block sieht so aus:
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 |
package de.wenzlaff.blockchain.be; import java.util.Arrays; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; /** * Der Block einer Blockchain. * * Ein einfaches POJO. Es können alle Daten enthalten sein. Hier mal als String. * * @author Thomas Wenzlaff */ public class Block { private static final Logger LOG = LogManager.getLogger(Block.class); /** * Der Hash vom vorherigen Block. */ private int previousHash; /** * Die Daten. Es könnte alles sein. */ private String data; /** * Der Hash dieses Blockes. */ private int hash; public Block(String data, int previousHash) { this.data = data; this.previousHash = previousHash; // eine Verbindung der gehashten Daten (mit Arrays.hashCode) mit den gehashten // Daten aus dem // vorhergehenden Block also das macht die Blockchain aus this.hash = Arrays.hashCode(new Integer[] { data.hashCode(), previousHash }); LOG.info("Neuen Block erzeugt mit Hash: " + this.hash); } public int getPreviousHash() { return previousHash; } public void setPreviousHash(int previousHash) { this.previousHash = previousHash; } public String getData() { return data; } public void setData(String data) { this.data = data; } public int getHash() { return hash; } public void setHash(int hash) { this.hash = hash; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("Block [previousHash="); builder.append(previousHash); builder.append(", "); builder.append("hash="); builder.append(hash); if (data != null) { builder.append(", "); builder.append("data="); builder.append(data); } builder.append("]"); return builder.toString(); } } |
Die Blockchain:
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 |
package de.wenzlaff.blockchain; import java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import de.wenzlaff.blockchain.be.Block; /** * Blockchain Test. * * @author Thomas Wenzlaff */ public class Blockchain { private static final Logger LOG = LogManager.getLogger(Blockchain.class); /** Die Blockchain. */ private List<Block> chain; public Blockchain() { chain = new ArrayList<>(); } public boolean add(Block block) { return chain.add(block); } public boolean checkBlockchain() { boolean ergebnis = validate(); if (ergebnis) { LOG.info("Die Blockchain ist gültig."); } else { LOG.error("Die Blockchain ist gehackt worden und ungültig."); } return ergebnis; } public void printBlockchain() { chain.forEach(b -> LOG.info(b)); } public int getPreviousHash() { return chain.get(getAnzahlBlocks() - 1).getHash(); } public Block get(int i) { return chain.get(i); } public void remove(int i) { chain.remove(i); } public void add(int i, Block block) { chain.add(i, block); } public int getAnzahlBlocks() { return chain.size(); } public boolean validate() { boolean result = true; Block lastBlock = null; for (int i = getAnzahlBlocks() - 1; i >= 0; i--) { if (lastBlock == null) { lastBlock = get(i); } else { Block current = get(i); if (lastBlock.getPreviousHash() != current.getHash()) { result = false; break; } lastBlock = current; } } return result; } } |
Die Junit 5 Testklasse mit den beiden Testfällen:
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 |
package de.wenzlaff.blockchain; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import de.wenzlaff.blockchain.be.Block; /** * Testet die Blockchain. * * @author Thomas Wenzlaff * */ class BlockchainTest { private static final Logger LOG = LogManager.getLogger(BlockchainTest.class); /** Die zu testende Blockchain. */ private Blockchain chain; @BeforeEach void iniBlockchain() { chain = new Blockchain(); Block genesisBlock = new Block("Erster Block (genesis) der Blockchain", 0); chain.add(genesisBlock); Block zweiterBlock = new Block("Zweiter Block", chain.getPreviousHash()); chain.add(zweiterBlock); Block dritterBlock = new Block("Dritter Block mit Daten", chain.getPreviousHash()); chain.add(dritterBlock); Block vierterBlock = new Block("Vierter Block auch mit super Daten.", chain.getPreviousHash()); chain.add(vierterBlock); Block letzterBlock = new Block("5. und letzter Block", chain.getPreviousHash()); chain.add(letzterBlock); LOG.info("Blockchain mit " + chain.getAnzahlBlocks() + " Blöcken für Test erzeugt."); } /** * Positiver Test. */ @Test void testBlockchain() { chain.printBlockchain(); assertTrue(chain.checkBlockchain()); } /** * Test wie eine veränderte Blockchain erkannt wird: ersetzen des zweiten Block * durch einen veränderten bzw. gehackten Block. Es passen die Hash nicht mehr */ @Test void testBlockchainHack() { Block gehackterBlock = new Block("Verfälschte Daten", chain.get(0).getHash()); chain.remove(1); chain.add(1, gehackterBlock); chain.printBlockchain(); assertFalse(chain.checkBlockchain()); } } |
Die Ausgabe des pos. Tests:
1 2 3 4 5 6 7 8 9 10 11 12 |
[INFO ] 2021-04-13 22:20:20,309 Block.<init>() - Neuen Block erzeugt mit Hash: -465238321 [INFO ] 2021-04-13 22:20:20,312 Block.<init>() - Neuen Block erzeugt mit Hash: -870077405 [INFO ] 2021-04-13 22:20:20,312 Block.<init>() - Neuen Block erzeugt mit Hash: 1814193111 [INFO ] 2021-04-13 22:20:20,312 Block.<init>() - Neuen Block erzeugt mit Hash: 791461576 [INFO ] 2021-04-13 22:20:20,312 Block.<init>() - Neuen Block erzeugt mit Hash: 1378020438 [INFO ] 2021-04-13 22:20:20,312 BlockchainTest.iniBlockchain() - Blockchain mit 5 Blöcken für Test erzeugt. [INFO ] 2021-04-13 22:20:20,315 Blockchain.lambda$printBlockchain$0() - Block [previousHash=0, hash=-465238321, data=Erster Block (genesis) der Blockchain] [INFO ] 2021-04-13 22:20:20,315 Blockchain.lambda$printBlockchain$0() - Block [previousHash=-465238321, hash=-870077405, data=Zweiter Block] [INFO ] 2021-04-13 22:20:20,315 Blockchain.lambda$printBlockchain$0() - Block [previousHash=-870077405, hash=1814193111, data=Dritter Block mit Daten] [INFO ] 2021-04-13 22:20:20,315 Blockchain.lambda$printBlockchain$0() - Block [previousHash=1814193111, hash=791461576, data=Vierter Block auch mit super Daten.] [INFO ] 2021-04-13 22:20:20,315 Blockchain.lambda$printBlockchain$0() - Block [previousHash=791461576, hash=1378020438, data=5. und letzter Block] [INFO ] 2021-04-13 22:20:20,316 Blockchain.checkBlockchain() - Die Blockchain ist gültig. |
Und der Test, wenn jemand versucht die Blockchain zu hacken:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[INFO ] 2021-04-13 22:21:31,821 Block.<init>() - Neuen Block erzeugt mit Hash: -465238321 [INFO ] 2021-04-13 22:21:31,823 Block.<init>() - Neuen Block erzeugt mit Hash: -870077405 [INFO ] 2021-04-13 22:21:31,824 Block.<init>() - Neuen Block erzeugt mit Hash: 1814193111 [INFO ] 2021-04-13 22:21:31,824 Block.<init>() - Neuen Block erzeugt mit Hash: 791461576 [INFO ] 2021-04-13 22:21:31,824 Block.<init>() - Neuen Block erzeugt mit Hash: 1378020438 [INFO ] 2021-04-13 22:21:31,824 BlockchainTest.iniBlockchain() - Blockchain mit 5 Blöcken für Test erzeugt. [INFO ] 2021-04-13 22:21:31,826 Block.<init>() - Neuen Block erzeugt mit Hash: 1371965362 [INFO ] 2021-04-13 22:21:31,827 Blockchain.lambda$printBlockchain$0() - Block [previousHash=0, hash=-465238321, data=Erster Block (genesis) der Blockchain] [INFO ] 2021-04-13 22:21:31,827 Blockchain.lambda$printBlockchain$0() - Block [previousHash=-465238321, hash=1371965362, data=Verfälschte Daten] [INFO ] 2021-04-13 22:21:31,827 Blockchain.lambda$printBlockchain$0() - Block [previousHash=-870077405, hash=1814193111, data=Dritter Block mit Daten] [INFO ] 2021-04-13 22:21:31,827 Blockchain.lambda$printBlockchain$0() - Block [previousHash=1814193111, hash=791461576, data=Vierter Block auch mit super Daten.] [INFO ] 2021-04-13 22:21:31,827 Blockchain.lambda$printBlockchain$0() - Block [previousHash=791461576, hash=1378020438, data=5. und letzter Block] [ERROR] 2021-04-13 22:21:31,828 Blockchain.checkBlockchain() - Die Blockchain ist gehackt worden und ungültig. |
Das ganze Projekt kann hier als Maven Beispielprojekt (branch 0.0.1) von GitLab geladen werden.