Bouw een set blockchains met behulp van Java PlatoBlockchain Data Intelligence. Verticaal zoeken. Ai.

Bouw een set blockchains met Java

Vertrouwen via peer-to-peer-netwerken

spinnenweb
Afbeelding door Claudia Wollesen oppompen van Pixabay

In het eerste deel van deze serie hebben we één blockchain gemaakt. Nu gaan we er een set van maken en ze met elkaar laten praten. Het echte punt van de blockchain is een gedistribueerd verificatiesysteem. Je kunt blokken van alle knooppunten toevoegen en uiteindelijk komt het bij peer-knooppunten, zodat iedereen het eens is over hoe de blockchain eruit ziet. Dit is belangrijk omdat je geen enkele bron van waarheid wilt in een gedistribueerd systeem.

Er is één probleem dat meteen opkomt: elk knooppunt bestaat uit twee services, plus een MongoDB en een Kafka-berichtenbus die allemaal met elkaar moeten praten. Maar ik wil meerdere nodes testen en demonstreren op een enkele host (mijn laptop). Ik heb met Docker compose gewerkt, dus ik zal voor elk knooppunt één Docker-compositiebestand moeten maken om de poorten recht te houden.

We werken aan een knooppuntservice waarmee de knooppunten met elkaar kunnen samenwerken. Dit krijgt invoer van twee plaatsen, een rustgevende interface waarmee u de aangesloten knooppunten kunt toevoegen en weergeven, en een berichtenbus van Kafka die de knooppuntservice op de hoogte stelt van wijzigingen in de lokale blockchain die naar de peer-knooppunten moeten worden uitgezonden.

Om ervoor te zorgen dat de services als afbeeldingen kunnen worden gebruikt, gebruik ik de Google Jib maven-plug-in. Dit is de eenvoudigste manier om een ​​afbeelding te maken van een maven-build. We voegen het volgende toe aan het pom-bestand van elke module waarvoor we een afbeelding nodig hebben:




com.google.cloud.tools
jib-maven-plugin
2.7.1


In ons geval kunnen we standaardwaarden gebruiken voor de configuratie. Vervolgens kun je rennen mvn clean install jib:build en het zal een afbeelding maken die u kunt gebruiken in uw Docker-opstelbestand.

Laten we nemen wat we tot nu toe hebben en het allemaal beginnen met een allesomvattend Docker-opstelbestand genaamd docker-compose-node1:

versie: '3.1'
diensten:
Mongo:
afbeelding: mongo
herstart: altijd
poorten:
- 27017
milieu:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: voorbeeld
Mongo-express:
afbeelding: mongo-express
herstart: altijd
poorten:
- 8081: 8081
milieu:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: voorbeeld
dierentuinmedewerker:
afbeelding: confluentinc/cp-dierenverzorger: laatste:
poorten:
- 2181
milieu:
ZOOKEEPER_CLIENT_POORT: 2181
kafka:
afbeelding: confluentinc/cp-kafka:laatste
poorten:
- 9092
- 29092
links:
- dierentuinmedewerker
milieu:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: dierenverzorger:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
blokketen:
afbeelding: rlkamradt/blockchain:1.0-SNAPSHOT
poorten:
- 8080: 8080
milieu:
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092

Je zult merken dat er een aantal dingen zijn veranderd. Eerst heb ik de externe poort van Kafka verwijderd, zodat deze alleen toegankelijk is binnen het samengestelde netwerk. Vervolgens heb ik de blockchain-app toegevoegd met de afbeelding die door de build is gemaakt. Ten slotte heb ik een paar veereigenschappen overschreven met de omgevingsvariabele, zodat deze toegang krijgt tot mongo en Kafka vanuit het samengestelde netwerk. Compose maakt DNS-vermeldingen zodat services die vanuit hetzelfde opstelbestand worden uitgevoerd, toegang tot elkaar hebben. Voer het uit met deze opdracht docker compose -f docker-compose-node1.yaml up -d en zorg ervoor dat je nog steeds de basis blockchain-API kunt raken:

Nu is onze applicatie in gebruik en heeft het genesis-blok gemaakt.

Er is al code in de blockchain-service die een bericht naar Kafka stuurt wanneer we een blok toevoegen of een transactie toevoegen. We moeten een nieuwe service maken die deze evenementen leest en uitzendt naar een lijst met peers. Laten we eens kijken wat we nu hebben draaien en een eenvoudige node-service toevoegen die een bericht logt wanneer het wordt ontvangen. We hebben een nieuwe module in het project nodig - dit wordt een andere Spring Boot-service en deze kan met externe knooppunten praten.

Eerst moeten we een knooppunt definiëren, dat slechts een URL is. Hier is de Node:

@Gegevens
@Bouwer
@AllArgsConstructeur
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = waar)
@Document
openbare klasse Knooppunt {
@ID kaart
privé String-ID;
privé String-url;
}

We slaan het op in een verzameling in de MongoDB, dus we hebben een repository-interface nodig:

@Opslagplaats
openbare interface NodeRepository breidt uit
ReactiveMongoRepository {
Mono findByUrl(String-URL);
}

Vervolgens hebben we een controller nodig om te kunnen zien en toevoegen Node voorwerpen:

@Slf4j
@RestController
@RequestMapping("/node")
openbare klasse SimpleNodeController {
privé finale Blockchain-blockchain;
privé definitief SimpleNodeService simpleNodeService;

openbare SimpleNodeController (Blockchain-blockchain,
SimpleNodeService simpleNodeService) {
this.simpleNodeService = simpleNodeService;
deze.blockchain = blockchain;
}

@GetMapping(path = "peers", produceert =
Mediatype.TEXT_EVENT_STREAM_VALUE)
flux getNodes() {
retourneer simpleNodeService.getPeers();
}

@PostMapping(path = "peers", produceert =
Mediatype.APPLICATION_JSON_VALUE)
Mono addNode(@RequestBody Node-knooppunt) {
retourneer simpleNodeService.connectToPeer (knooppunt);
}

}

Dan hebben we een eenvoudige serviceklasse nodig die zal communiceren met MongoDB en Kafka:

@Onderdeel
@Slf4j
openbare klasse SimpleNodeService {
privé statisch knooppunt mezelf;
laatste privé String-host;
laatste privé int-poort;
laatste private NodeRepository-peers;
laatste private Blockchain-blockchain;
laatste privé ReactiveKafkaConsumerTemplate
Bericht> zender;

public SimpleNodeService(@Value("${server.host}") String host,
@Value("${server.port}") String-poort,
Blockchain-blockchain,
NodeRepository-peers,
ReactiveKafkaConsumerTemplate zender) {
deze.host = host;
deze.poort = Integer.ontleedInt(haven);
deze.blockchain = blockchain;
deze.emitter = emitter;
deze.peers = peers;
mezelf = Knooppunt.bouwer()
.url("http://" + host + ":" + poort)
.bouwen();
emitter
.receiveAutoAck()
.doOnNext(consumerRecord -> inloggen.info(
"ontvangen sleutel={}, waarde={} van topic={}, offset={}",
consumentRecord.key(),
consumentRecord.waarde(),
consumentRecord.topic(),
consumentRecord.offset())
)
.map(ConsumentenRecord::waarde)
.abonneren(
m -> inloggen.info("bericht ontvangen {}", m),
e -> inloggen.error("fout bij het ontvangen van bericht", e));
}

openbare flux getPeers() {
retourneer peers.findAll();
}

openbare mono connectToPeer(Knooppunt) {
retourneer peers.save (knooppunt);
}
}

Elk bericht dat van Kafka wordt ontvangen, wordt gewoon gelogd en we zullen eigenlijk niets met de knooppunten doen, behalve ze opslaan en weergeven.

Ten slotte moeten we Spring Boot vertellen waar het gedeelde componenten en repositories kan vinden. We kunnen de hoofdklasse annoteren:

@Slf4j
@SpringBootApplicatien
@ComponentScan(basePackageClasses = {
net.kamradtfamily.blockchain.api.Blockchain.class,
net.kamradtfamily.blockchainnode.Application.class})
@EnableReactiveMongoRepositories (basePackageClasses = {
net.kamradtfamily.blockchain.api.BlockRepository.class,
net.kamradtfamily.blockchainnode.NodeRepository.class})
openbare klasse Toepassing {
public static void main (String [] args) {
SpringApplication.run (Application.class, args);
proberen {
Eigenschappen gitProps = nieuwe Eigenschappen();
gitProps.load(
Aanvraag
.klasse
.getResourceAsStream("/git.properties"));
log.info("Git-eigenschappen:");
gitProps.entrySet().stream()
.forEach(es ->
log.info("{}: {}",
es.getKey(),
es.getValue()));
} vangst (uitzondering e) {
log.error("Fout bij het lezen van Git-eigenschappen");
}
}
}

De lente moet worden verteld waar ze naar componenten en repositories moeten scannen. Het ziet er meestal uit in het pakket waarin de hoofdklasse zich bevindt, maar in ons geval wilden we componenten delen van net.kamradtfamily.blockchain.api. Dus ik heb de . toegevoegd ComponentScan en EnableReactiveMongoRepositories annotaties. Ik heb ook wat logboekregistratie toegevoegd, dus wanneer het opstart, weten we welke Git-commit-hash we gebruiken.

Om dit allemaal uit te voeren, moeten we een aantal poorten verplaatsen. Om de nieuwe dienst en de bestaande dienst te hebben, zullen we elk van hen unieke externe poorten moeten geven. Laten we dat toevoegen aan onze docker-compose-node1.yaml:

blockchainknooppunt:
afbeelding: rlkamradt/blockchainnode:1.0-SNAPSHOT
poorten:
- 8080: 8082
milieu:
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092

De MongoExpress-service neemt al poort 8081 in beslag, dus we stellen het bloot als 8082. Bouw nu de nieuwe afbeeldingen, trek ze op en voer ze allemaal uit:

mvn schone installatie jib:build
docker compose -f docker-compose-node1.yaml pull
docker compose -f docker-compose-node1.yaml omhoog

Wanneer u vervolgens een transactie maakt met de blockchain-service, ziet u in de logboeken van de blockchainnode service dat er een bericht is ontvangen. Je kunt ook de eindpunten bereiken http://localhost:8082/node/peers en maak en vermeld peers.

Hier wordt het ingewikkeld. We hebben meer dan één knooppunt nodig en we hebben de knooppunten nodig om te reageren op berichten na transacties of blokkeringen. We hebben de nodes ook nodig om met elkaar te praten tijdens het opstarten of het toevoegen van nodes. ik ga kopiëren SimpleNodeService en SimpleNodeController naar NodeService en NodeController. Ik ga de oude klassen achterlaten voor het geval je de code op GitHub bekijkt en wilt volgen, maar ik ga commentaar geven op de Component en RestController annotaties zodat ze niet tijdens runtime aan de slag kunnen.

Ik ga een extra eindpunt toevoegen aan de NodeController om bevestiging mogelijk te maken dat een transactie een blok heeft gemaakt in alle knooppunten:

@GetMapping(path = "transacties/:transactie-ID/bevestigingen",
produceert = MediaType.ALL_VALUE)
Mono getTransactionFromNode(
@RequestParam("transactionId") String transactionId) {
return nodeService
.getConfirmations(Lang.waarde van(Transactie ID))
.map(b -> b.toString());
}

Dit betekent dat ik een nieuwe set methoden nodig heb in de NodeService om de bevestigingen van alle knooppunten te krijgen:

openbare mono getConfirmation (node-peer, lange transactie-ID) {
String-URL = peer.getUrl()
+ "/block/blocks/transacties/"
+ transactie-ID;
inloggen.info("Transacties ontvangen van: {}", URL);
terugkerende klant
.krijgen()
.uri(URL)
.retrieve().bodyToMono(Blok.klasse);
.onErrorContinue((t, o) -> Mono.leeg());
}

Mono getConfirmations(lange transactie-ID) {
// Krijg het aantal peers met bevestigingen dat de transactie bestaat
retour blockchain
.findTransactionInChain(transactie-ID, blockchain)
.getAllBlocks())
.zipWith(peers.findAll()
.flatMap(peer -> getConfirmation(peer,
Transactie ID)))
.telling();
}

Dit retourneert een telling van de knooppunten die deze transactie in een blok hebben. Maar eerst moet ik een nieuw eindpunt maken in de BlockController, een die rapporteert of de transactie zich in een blok in de blockchain bevindt.

@GetMapping(path = "blocks/transaction/{transactionId}", 
produceert = MediaType.APPLICATION_JSON_VALUE)
openbare mono getTransaction(
@PathVariable("transactionId") Lange transactionId) {
retour blockchain
.findTransactionInChain(transactie-ID,
blockchain.getAllBlocks())
.last() // neem aan dat er maar één is
.switchIfEmpty(Mono.fout(nieuwe ResponseStatusException(
HTTP-status.NIET GEVONDEN,
"Transactie niet gevonden in Blockchain")));

Gelukkig hebben we al een methode findTransactionInChain die het blok retourneert waarin een transactie is gevonden.

Vervolgens moeten we reageren op berichten van Kafka. We voegen een toe messageHandler methode die berichten naar alle peer-knooppunten zal uitzenden:

openbare mono messageHandler(Bericht m) {
if("addedBlock".is gelijk aan(m.getMessage())) {
retourneer peers.findAll()
.flatMap(p -> sendLatestBlock(p, m.getBlock()))
.switchIfEmpty(Mono.just(m.getBlock()))
.laatste();
} else if("addedTransaction".equals(m.getMessage())) {
retourneer peers.findAll()
.flatMap(p -> sendTransaction(p, m.getTransaction()))
.switchIfEmpty(Mono.just(m.getTransaction()))
.laatste();
} else if("getBlocks".equals(m.getMessage())) {
retourneer peers.findAll()
.flatMap(p -> getBlocks(p))
.switchIfEmpty(blockchain.getLastBlock())
.laatste();
} Else {
log.error("onbekend bericht {}", m);
retourneer Mono.leeg();
}
}

Dit vereist twee nieuwe methoden om verzoeken aan andere knooppunten te doen:

openbare mono sendLatestBlock (Node-peer, 
Blok blok) {
String URL = peer.getUrl() + "/block/blocks/latest";
inloggen.info("Laatste blok plaatsen op: {}", URL);
terugkerende klant
.leggen()
.uri(URL)
.body(blok, blok.klasse)
.uitwisseling();
}

openbare mono sendTransaction (Node-peer,
Transactie transactie) {
String-URL = peer.getUrl() + "/transactie";
inloggen.info("Transactie '{}' verzenden naar: {}", transactie, URL);
terugkerende klant
.na()
.uri(URL)
.body(transactie, Transaction.class)
.uitwisseling();
}

We hebben al de POST naar de /transaction eindpunt, maar we moeten de PUT toevoegen aan de /block/blocks/latest eindpunt.

@PutMapping(path = "/block/blocks/latest", 
produceert = MediaType.APPLICATION_JSON_VALUE)
openbare mono checkReceivedBlock(
@RequestBody Block ontvangenBlock) {
retourneer blockchain.checkReceivedBlock(receivedBlock);
}

Dit vraagt ​​om een ​​nieuwe methode in de Blockchain service te halen.

openbare mono checkReceivedBlock(Blok ontvangenBlock) {
retourneer getLastBlock()
.filter(b -> b.getIndex() < ReceivedBlock.getIndex())
.flatMap(b -> {
log.info(
"Blockchain mogelijk achter. We hebben: {}, Peer heeft: {}",
b.getIndex(),
ontvangenBlock.getIndex());
if (b.getHash().is gelijk aan(receivedBlock.getPreviousHash())) {
log.info("Ontvangen blok toevoegen aan onze keten");
return addBlock (receivedBlock, true);
} Else {
log.info ("Query-keten van onze collega's");
emitter.send(ONDERWERP, Bericht.builder()
.type("leeg")
.message("getBlocks")
.bouwen())
.abonneren();
retourneer Mono.leeg();
}
});
}

U kunt zien hoe de knooppuntlaag met andere knooppunten praat via de openbare API. Er is echter één probleem. Elk knooppunt wordt vertegenwoordigd door een enkele URL, maar we moeten met twee afzonderlijke services praten: de blokservice en de knooppuntservice. Ik ga een eenvoudige instap maken met een Nginx-instantie. Op die manier kunnen we met beide services (en later meer) praten met een enkele URL. Je kunt de code op GitHub bekijken voor details over de Nginx-configuratie en het toevoegen van alle services aan de docker-compose-node1.yaml.

Als ik alles opstart, werken alle eindpunten nog steeds en ik kan in de logs de communicatie zien tussen de blockchain-service en de node-service op de Kafka-bus. Nu is het tijd om een ​​tweede knoop te maken. Kopieer de docker-compose-node1.yaml naar docker-compose-node2.yaml en schakel de externe poort van de Nginx-service van 8080 naar 8081 om knooppunt 1 op poort 8080 en knooppunt 2 op poort 8081 te bereiken. Ik ga ook een klein script maken met de naam startnode1 om elke service in volgorde te starten en de logs van de node-service uit te voeren:

docker compose -p node1 -f docker-compose-node1.yaml omhoog -d mongo
docker compose -p node1 -f docker-compose-node1.yaml up -d dierenverzorger
docker compose -p node1 -f docker-compose-node1.yaml omhoog -d mongo-express
docker compose -p node1 -f docker-compose-node1.yaml omhoog -d kafka
docker compose -p node1 -f docker-compose-node1.yaml up -d blockchain
docker compose -p node1 -f docker-compose-node1.yaml up blockchainnode

Omdat de laatste regel geen heeft -d vlag, het toont de logs totdat Ctrl-C het stopt. ik gebruik de -p node1 flag zodat ik afzonderlijke instanties van de services kan maken. Kopieer vervolgens naar een bestand met de naam startnode2, maar vervang het Docker-opstelbestand om node 2 op te starten en de -p vlag naar knooppunt2. Vergeet niet om de uitvoerbare vlag op elk in te stellen:

chmod +x startknooppunt1
chmod +x startknooppunt2

Er is nog een laatste wijziging. Het mezelf-lid van de node-service moet de URL hebben zoals deze door andere services wordt gezien, dus het gebruik van een lokale host is niet voldoende. Ik heb een Spring-eigenschap ingesteld in de application.properties:

server.mijnzelf: http://localhost:8080

Dan overschrijf ik het in de docker-compose-node1.yaml die er nu als volgt uitziet:

versie: '3.1'
diensten:
Mongo:
afbeelding: mongo
herstart: altijd
poorten:
- 27017
milieu:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: voorbeeld
dierentuinmedewerker:
afbeelding: confluentinc/cp-dierenverzorger: laatste:
poorten:
- 2181
milieu:
ZOOKEEPER_CLIENT_POORT: 2181
kafka:
afbeelding: confluentinc/cp-kafka:laatste
poorten:
- 9092
- 29092
links:
- dierentuinmedewerker
milieu:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: dierenverzorger:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
blokketen:
afbeelding: rlkamradt/blockchain:1.0-SNAPSHOT
poorten:
- 8080
milieu:
SERVER_MYSELF: http://192.168.0.174:8080
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092
blockchainknooppunt:
afbeelding: rlkamradt/blockchainnode:1.0-SNAPSHOT
poorten:
- 8080
milieu:
SERVER_MYSELF: http://192.168.0.174:8080
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092
nginx:
afbeelding: nginx:laatste
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
poorten:
- 8080: 80

De docker-compose-node2.yaml verandert ook de poort in 8081 voor zowel de SERVER_MYSELF en nginx.ports waarde.

Start beide instanties op. Als ze allebei actief zijn, kun je de een met de ander verbinden:

Nu kunt u transacties maken en blokken minen, zoals aangetoond in de vorige artikel, maar u kunt de transacties en blokken in beide knooppunten weergeven. Het peer-to-peer protocol zorgt ervoor dat beide nodes over dezelfde data beschikken.

Ik zal niet zeggen dat het op dit moment perfect is. Er zijn veel verschillende reeksen die moeten worden getest om ervoor te zorgen dat, ongeacht hoe dingen worden gedaan, de blockchain in de verschillende instanties hetzelfde blijft. Maar dit artikel is al lang genoeg en ik weet zeker dat je niet wilt lezen dat ik dit web debugged!

Bedankt voor het lezen van dit nogal lange artikel, ik heb geprobeerd het samen te vatten om het zo beknopt mogelijk te maken, maar het is een erg ingewikkeld onderwerp.

Ik denk dat het volgende artikel in deze serie iets eenvoudiger zal zijn. We zullen het laatste deel van de puzzel bespreken: miners en gebruikers.

Alle code voor dit artikel is hier te vinden:

Het vorige artikel in deze serie:

Bron: https://betterprogramming.pub/build-a-set-of-blockchains-using-java-d99cd866931b?source=rss——-8—————–cryptocurrency

Tijdstempel:

Meer van Medium