Készítsen blokklánc-készletet a Java PlatoBlockchain Data Intelligence segítségével. Függőleges keresés. Ai.

Készítsen blokklánc-készletet Java segítségével

Megbízhatóság peer-to-peer hálózaton keresztül

pókháló
Kép ClaudiaWollesen ból ből pixabay

A sorozat első részében egyetlen blokkláncot készítettünk. Most készítünk belőlük egy készletet, és megbeszéljük egymással. A blokklánc valódi lényege egy elosztott ellenőrzési rendszer. Bármely csomópontból hozzáadhat blokkokat, és végül az egyenrangú csomópontokhoz kerül, így mindenki egyetért abban, hogy hogyan néz ki a blokklánc. Ez azért fontos, mert nem akar egyetlen igazságforrást egy elosztott rendszerben.

Van egy probléma, amely azonnal felmerül: minden csomópont két szolgáltatásból áll, valamint egy MongoDB és egy Kafka üzenetbuszból, amelyeknek beszélniük kell egymással. De több csomópontot szeretnék tesztelni és demonstrálni egyetlen gazdagépen (a laptopomon). A Docker-írással futottam, ezért minden csomóponthoz készítenem kell egy Docker-összeállítási fájlt, hogy a portok egyenesen maradjanak.

Dolgozunk egy csomópont-szolgáltatáson, amely lehetővé teszi, hogy a csomópontok együttműködjenek egymással. Ez két helyről fog bemenetet kapni: egy pihentető felületről, amely lehetővé teszi a csatlakoztatott csomópontok hozzáadását és listázását, valamint a Kafka által biztosított üzenetbuszról, amely értesíti a csomóponti szolgáltatást a helyi blokklánc változásairól, amelyeket továbbítani kell a peer csomópontoknak.

Ahhoz, hogy a szolgáltatásokat képként használhassam, a Google Jib maven beépülő modult fogom használni. Ez a legegyszerűbb módja a kép létrehozásának egy maven buildből. Minden olyan modul pom fájljához hozzáadjuk a következőket, amelyekhez képre van szükségünk:




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


Esetünkben alapértelmezett értékeket használhatunk a konfigurációhoz. Ezután futhat mvn clean install jib:build és létrehoz egy képet, amelyet a Docker-összeállítási fájlban használhat.

Vegyük az eddigieket, és kezdjük el az egészet egy mindent magában foglaló Docker-kompozíciós fájllal docker-compose-node1:

verzió: '3.1'
szolgáltatások:
mongo:
kép: mongo
újraindítás: mindig
portok:
- 27017
környezet:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: példa
mongo-expressz:
kép: mongo-express
újraindítás: mindig
portok:
- 8081: 8081
környezet:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: példa
állatgondozó:
kép: confluentinc/cp-zookeeper:latest
portok:
- 2181
környezet:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
kép: confluentinc/cp-kafka:latest
portok:
- 9092
- 29092
linkek:
- állatgondozó
környezet:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: állatkert: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
blokklánc:
kép: rlkamradt/blockchain:1.0-SNAPSHOT
portok:
- 8080: 8080
környezet:
MONGO_HOST: mongó
SPRING_KAFKA_BOOTSTRAP-SZERVEREK: kafka:29092

Észre fogja venni, hogy néhány dolog megváltozott. Először is eltávolítottam a külső portot a Kafkából, így csak a levélírási hálózaton belül lesz elérhető. Ezután hozzáadtam a blokklánc alkalmazást a build által létrehozott képpel. Végül felülírtam néhány rugó tulajdonságot a környezeti változóval, hogy a mongo-t és a Kafkát a kompose hálózaton belülről érje el. A Compose DNS-bejegyzéseket hoz létre, hogy az ugyanabból a szerkesztési fájlból futó szolgáltatások hozzáférhessenek egymáshoz. Futtassa ezzel a paranccsal docker compose -f docker-compose-node1.yaml up -d és győződjön meg arról, hogy továbbra is elérheti az alapvető blokklánc API-t:

Most az alkalmazásunk működik, és létrehozta a genezis blokkot.

A blokklánc szolgáltatásban már van kód, amely üzenetet küld Kafkának, amikor blokkot adunk hozzá vagy tranzakciót adunk hozzá. Létre kell hoznunk egy új szolgáltatást, amely elolvassa ezeket az eseményeket, és közvetíti azokat a társak listájának. Nézzük meg, hogy mi fut jelenleg, és adjunk hozzá egy egyszerű csomópont-szolgáltatást, amely naplózza az üzenetet, amikor megérkezik. Szükségünk lesz egy új modulra a projektben – ez egy másik Spring Boot szolgáltatás lesz, és képes lesz kommunikálni a külső csomópontokkal.

Először is meg kell határoznunk egy csomópontot, amely csak egy URL. Itt van a Node:

@Adat
@Építész
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = igaz)
@Dokumentum
public class Node {
@Id
privát karakterlánc azonosítója;
privát karakterlánc url;
}

Egy gyűjteményben tároljuk a MongoDB-ben, ezért szükségünk van egy tárfelületre:

@Repository
nyilvános felület A NodeRepository kibővül
ReactiveMongoRepository {
Monó findByUrl(String url);
}

Ezután szükségünk van egy vezérlőre, hogy láthassuk és hozzáadjuk Node tárgyak:

@Slf4j
@RestController
@RequestMapping("/csomópont")
public class SimpleNodeController {
privát végleges Blockchain blokklánc;
privát végleges SimpleNodeService simpleNodeService;

public SimpleNodeController(Blockchain blokklánc,
SimpleNodeService simpleNodeService) {
this.simpleNodeService = simpleNodeService;
this.blockchain = blokklánc;
}

@GetMapping(útvonal = "társ", produkálja =
Média típus.TEXT_EVENT_STREAM_VALUE)
Fényáram getNodes() {
return simpleNodeService.getPeers();
}

@PostMapping(path = "peers", produkálja =
Média típus.APPLICATION_JSON_VALUE)
Monó addNode(@RequestBody Node node) {
return simpleNodeService.connectToPeer(node);
}

}

Ezután szükségünk van egy egyszerű szolgáltatási osztályra, amely kölcsönhatásba lép a MongoDB-vel és a Kafkával:

@Összetevő
@Slf4j
public class SimpleNodeService {
privát statikus csomópont magamat;
végső privát String host;
végső privát belső kikötő;
végső privát NodeRepository társak;
végső privát Blockchain blokklánc;
végső privát ReactiveKafkaConsumerTemplate
Üzenet> kibocsátó;

public SimpleNodeService(@Value("${server.host}") String gazdagép,
@Value("${server.port}") Karakterlánc port,
Blockchain blokklánc,
NodeRepository társak,
ReactiveKafkaConsumerTemplate kibocsátó) {
this.host = host;
this.port = Egész szám.parseInt(kikötő);
this.blockchain = blokklánc;
this.emitter = emitter;
this.peers = társak;
magamat = Csomópont.építész()
.url("http://" + gazdagép + ":" + port)
.épít();
emitter
.receiveAutoAck()
.doOnNext(consumerRecord -> log.info(
"received key={}, value={} from topic={}, offset={}",
userRecord.key(),
userRecord.value(),
fogyasztóRecord.topic(),
fogyasztóiRekord.offset())
)
.map(ConsumerRecord::value)
.Iratkozz fel(
m -> log.info("kapott üzenet: {}", m),
e -> log.error("hiba az üzenet fogadásakor", e));
}

nyilvános Flux getPeers() {
return peers.findAll();
}

nyilvános Mono connectToPeer(Node node) {
return peers.save(node);
}
}

A Kafkától kapott üzenetek egyszerűen naplózásra kerülnek, és valójában nem teszünk semmit a csomópontokkal, kivéve a tárolást és a listázást.

Végül meg kell mondanunk a Spring Bootnak, hogy hol találja meg a megosztott összetevőket és adattárakat. Megjegyezhetjük a fő osztályt:

@Slf4j
@SpringBootApplication
@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})
nyilvános osztály jelentkezés {
public static void main(String [] args) {
SpringApplication.run(Application.class, args);
próbáld ki {
Tulajdonságok gitProps = new Properties();
gitProps.load(
Alkalmazás
.osztály
.getResourceAsStream("/git.properties"));
log.info("Git tulajdonságai:");
gitProps.entrySet().stream()
.forEach(es ->
log.info("{}: {}",
es.getKey(),
es.getValue()));
} fogás (e kivétel) {
log.error("Hiba a Git tulajdonságainak olvasásakor");
}
}
}

A Springnek meg kell mondani, hogy hol keressen komponenseket és adattárakat. Általában úgy tűnik, hogy a csomagban a fő osztály található, de esetünkben meg akartuk osztani az összetevőket net.kamradtfamily.blockchain.api. Szóval hozzátettem a ComponentScan és a EnableReactiveMongoRepositories megjegyzések. Hozzáadtam néhány naplózást is, így amikor elindul, tudni fogjuk, hogy milyen Git commit hash-t futtatunk.

Mindezek futtatásához néhány portot át kell helyeznünk. Ahhoz, hogy az új szolgáltatás és a meglévő szolgáltatás elérhető legyen, mindegyiknek egyedi külső portot kell biztosítanunk. Tegyük hozzá a miénkhez docker-compose-node1.yaml:

blockchainnode:
kép: rlkamradt/blockchainnode:1.0-SNAPSHOT
portok:
- 8080: 8082
környezet:
MONGO_HOST: mongó
SPRING_KAFKA_BOOTSTRAP-SZERVEREK: kafka:29092

A MongoExpress szolgáltatás már használja a 8081-es portot, ezért 8082-esként tesszük közzé. Most készítse el az új képeket, húzza ki őket, és futtassa mindet:

mvn tiszta telepítés jib:build
docker compose -f docker-compose-node1.yaml pull
docker compose -f docker-compose-node1.yaml fel

Majd amikor tranzakciót hoz létre a blokklánc szolgáltatással, a naplókban látni fogja a blockchainnode szolgáltatást, hogy üzenet érkezett. A végpontokat is elérheti http://localhost:8082/node/peers valamint társak létrehozása és listázása.

Itt bonyolódnak a dolgok. Egynél több csomópontra van szükségünk, és szükségünk van a csomópontokra, hogy válaszoljanak az üzenetekre a tranzakció vagy blokk hozzáadása után. Szükségünk van arra is, hogy a csomópontok beszéljenek egymással indításkor vagy csomópontok hozzáadásakor. Megyek másolni SimpleNodeService és a SimpleNodeController nak nek NodeService és a NodeController. Hagyom a régi osztályokat arra az esetre, ha a GitHubon nézegeted a kódot, és követni szeretnéd, de kommentálom a Component és a RestController megjegyzéseket, hogy ne futás közben induljanak el.

Hozzáadok egy további végpontot a NodeController annak megerősítésére, hogy egy tranzakció blokkká vált az összes csomópontban:

@GetMapping(útvonal = "tranzakciók/:transactionId/confirmations",
produkál = MediaType.ALL_VALUE)
Monó getTransactionFromNode(
@RequestParam("transactionId") String tranzakcióazonosító) {
return nodeService
.getConfirmations(Hosszú.értéke(Tranzakció azonosítója))
.map(b -> b.toString());
}

Ez azt jelenti, hogy új módszerkészletre van szükségem a NodeService hogy megkapja a megerősítéseket az összes csomóponttól:

nyilvános Mono getConfirmation(Node peer, long tranzakcióazonosító) {
String URL = peer.getUrl()
+ "/blokk/blokkok/tranzakciók/"
+ tranzakcióazonosító;
log.info("Tranzakciók lekérése innen: {}", URL);
visszatérő ügyfél
.kap()
.uri(URL)
.retrieve().bodyToMono(Block.class);
.onErrorContinue((t, o) -> Mono.üres());
}

Monó getConfirmations(hosszú tranzakcióazonosító) {
// A tranzakció létezését megerősítő partnerek számának lekérése
blokklánc visszaküldése
.findTransactionInChain(transactionId, blockchain
.getAllBlocks())
.zipWith(peers.findAll()
.flatMap(peer -> getConfirmation(peer,
Tranzakció azonosítója)))
.számol();
}

Ez visszaadja azoknak a csomópontoknak a számát, amelyeknél ez a tranzakció egy blokkban található. Először azonban létre kell hoznom egy új végpontot a BlockController, amely jelenteni fogja, ha a tranzakció egy blokkban van a blokkláncban.

@GetMapping(útvonal = "blokkok/tranzakció/{tranzakcióazonosító}", 
produkál = MediaType.APPLICATION_JSON_VALUE)
nyilvános Mono getTransaction(
@PathVariable("transactionId") Hosszú tranzakcióazonosító) {
blokklánc visszaküldése
.findTransactionInChain(transactionId,
blockchain.getAllBlocks())
.last() // tegyük fel, hogy csak egy van
.switchIfEmpty(Mono.hiba(new ResponseStatusException(
HttpStatus.NEM TALÁLHATÓ,
"Tranzakció nem található a blokkláncban")));

Szerencsére már van egy módszerünk findTransactionInChain amely azt a blokkot adja vissza, amelyben a tranzakció található.

Ezután Kafka üzeneteire kell válaszolnunk. Hozzáadjuk a messageHandler módszer, amely üzeneteket sugároz az összes peer csomóponthoz:

nyilvános Mono üzenetkezelő(m üzenet) {
if("addedBlock".equals(m.getMessage())) {
return peers.findAll()
.flatMap(p -> sendLatestBlock(p, m.getBlock()))
.switchIfEmpty(Mono.just(m.getBlock()))
.utolsó();
} else if("addedTransaction".equals(m.getMessage())) {
return peers.findAll()
.flatMap(p -> sendTransaction(p, m.getTransaction()))
.switchIfEmpty(Mono.just(m.getTransaction()))
.utolsó();
} else if("getBlocks".equals(m.getMessage())) {
return peers.findAll()
.flatMap(p -> getBlocks(p))
.switchIfEmpty(blockchain.getLastBlock())
.utolsó();
} Else {
log.error("ismeretlen üzenet {}", m);
return Mono.empty();
}
}

Ehhez két új módszerre van szükség a kérések más csomópontokhoz történő küldéséhez:

nyilvános Mono sendLatestBlock(Node peer, 
Blokk blokk) {
String URL = peer.getUrl() + "/block/blocks/latest";
log.info("Legfrissebb blokk közzététele ide: {}", URL);
visszatérő ügyfél
.put()
.uri(URL)
.body(blokk, blokk.osztály)
.csere();
}

nyilvános Mono sendTransaction(Node peer,
Tranzakciós tranzakció) {
String URL = peer.getUrl() + "/tranzakció";
log.info("Tranzakció '{}' küldése ide: {}", tranzakció, URL);
visszatérő ügyfél
.post()
.uri(URL)
.body(tranzakció, Tranzakció.osztály)
.csere();
}

Már megvan a POST a /transaction végpont, de hozzá kell adnunk a PUT-ot a /block/blocks/latest végpont.

@PutMapping(útvonal = "/block/blocks/latest", 
produkál = MediaType.APPLICATION_JSON_VALUE)
nyilvános Mono checkReceivedBlock(
@RequestBody Block ReceiveBlock) {
return blockchain.checkReceivedBlock(receivedBlock);
}

Ehhez új módszerre van szükség a Blockchain szolgáltatás.

nyilvános Mono checkReceivedBlock(Block ReceivedBlock) {
return getLastBlock()
.filter(b -> b.getIndex() < ReceivedBlock.getIndex())
.flatMap(b -> {
log.info(
"Blockchain valószínűleg lemaradt. Megkaptuk: {}, Peer kapott: {}",
b.getIndex(),
ReceiveBlock.getIndex());
if (b.getHash().equals(receivedBlock.getPreviousHash())) {
log.info("Fogadott blokk hozzáfűzése láncunkhoz");
return addBlock(receivedBlock, true);
} Else {
log.info("Lánc lekérdezése társainktól");
emitter.send(TOPIC, Message.builder()
.type("üres")
.message("getBlocks")
.épít())
.Iratkozz fel();
return Mono.empty();
}
});
}

Megtekintheti, hogy a csomóponti réteg hogyan kommunikál más csomópontokkal a nyilvános API-n keresztül. Egy probléma azonban van. Minden csomópontot egyetlen URL képvisel, de két külön szolgáltatással kell beszélnünk: a blokkszolgáltatással és a csomóponti szolgáltatással. Egy egyszerű belépést fogok végrehajtani egy Nginx-példánnyal. Így egyetlen URL-lel beszélhetünk mindkét szolgáltatással (és később többel is). A GitHubon található kódban megtekintheti az Nginx konfigurációjával és az összes szolgáltatás hozzáadásával kapcsolatos részleteket docker-compose-node1.yaml.

Mindent elindítva az összes végpont továbbra is működik, és a naplókban látom a kommunikációt a blokklánc szolgáltatás és a Kafka buszon lévő csomóponti szolgáltatás között. Itt az ideje egy második csomópont létrehozásának. Másolja a docker-compose-node1.yaml nak nek docker-compose-node2.yaml és állítsa át az Nginx szolgáltatás külső portját 8080-ról 8081-re, hogy elérje az 1-es csomópontot a 8080-as porton és a 2-es csomópontot a 8081-es porton. Készítek egy kis szkriptet is, az ún. startnode1 az egyes szolgáltatások sorrendben történő elindításához és a naplók kiadásához a csomóponti szolgáltatásból:

docker compose -p node1 -f docker-compose-node1.yaml up -d mongo
docker compose -p node1 -f docker-compose-node1.yaml up -d zookeeper
docker compose -p node1 -f docker-compose-node1.yaml up -d mongo-express
docker compose -p node1 -f docker-compose-node1.yaml up -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

Mert az utolsó sorban nincs a -d zászlót, addig jeleníti meg a naplókat, amíg a Ctrl-C le nem állítja. használom a -p node1 jelölje meg, hogy létrehozhassam a szolgáltatások külön példányait. Ezután másoljon egy nevű fájlba startnode2, de cserélje ki a Docker összeállítási fájlt a 2. csomópont indításához és a -p jelölje meg a 2. csomópontot. Ne felejtse el mindegyikhez beállítani a végrehajtható jelzőt:

chmod +x startnode1
chmod +x startnode2

Egy utolsó változás van. A csomópont szolgáltatás magam tagjának rendelkeznie kell a többi szolgáltatás által látott URL-címmel, így a helyi gazdagép használata nem megy. Beállítottam egy Spring tulajdonságot az application.properties-ben:

server.myself: http://localhost:8080

Ezután felülírom a docker-compose-node1.yaml fájlban, amely most így néz ki:

verzió: '3.1'
szolgáltatások:
mongo:
kép: mongo
újraindítás: mindig
portok:
- 27017
környezet:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: példa
állatgondozó:
kép: confluentinc/cp-zookeeper:latest
portok:
- 2181
környezet:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
kép: confluentinc/cp-kafka:latest
portok:
- 9092
- 29092
linkek:
- állatgondozó
környezet:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: állatkert: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
blokklánc:
kép: rlkamradt/blockchain:1.0-SNAPSHOT
portok:
- 8080
környezet:
SERVER_MYSELF: http://192.168.0.174:8080
MONGO_HOST: mongó
SPRING_KAFKA_BOOTSTRAP-SZERVEREK: kafka:29092
blockchainnode:
kép: rlkamradt/blockchainnode:1.0-SNAPSHOT
portok:
- 8080
környezet:
SERVER_MYSELF: http://192.168.0.174:8080
MONGO_HOST: mongó
SPRING_KAFKA_BOOTSTRAP-SZERVEREK: kafka:29092
nginx:
kép: nginx:latest
kötetek:
- ./nginx.conf:/etc/nginx/nginx.conf
portok:
- 8080: 80

A docker-compose-node2.yaml is módosítja a portot 8081-re mind a SERVER_MYSELF és a nginx.ports értéket.

Indítsa el mindkét példányt. Amikor mindkettő fut, csatlakoztathatja az egyiket a másikhoz:

Mostantól tranzakciókat és bányászati ​​blokkokat hozhat létre, amint az a előző cikk, de mindkét csomópontban felsorolhatja a tranzakciókat és a blokkokat. A peer-to-peer protokoll biztosítja, hogy mindkét csomópont ugyanazokkal az adatokkal rendelkezzen.

Nem mondom, hogy ez most tökéletes. Sok különböző szekvenciát kell tesztelni annak biztosítására, hogy a dolgok hogyan is történjenek, a blokklánc a különböző példányokban ugyanaz maradjon. De ez a cikk már elég hosszú, és biztos vagyok benne, hogy nem akarsz arról olvasni, hogy én hibakeresem ezt a webet!

Köszönöm, hogy elolvastad ezt a meglehetősen hosszú cikket, próbáltam tömöríteni, hogy a lehető legtömörebb legyen, de ez egy nagyon bonyolult téma.

Azt hiszem, a sorozat következő cikke egy kicsit egyszerűbb lesz. Megbeszéljük a rejtvény utolsó részét: a bányászokat és a felhasználókat.

A cikkhez tartozó összes kód itt található:

A sorozat előző cikke:

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

Időbélyeg:

Még több közepes