Bangun Satu Set Blockchain Menggunakan Java PlatoBlockchain Data Intelligence. Pencarian Vertikal. ai.

Bangun Satu Set Blockchain Menggunakan Java

Percaya melalui jaringan peer-to-peer

jaring laba-laba
Image by Claudia Wollesen dari Pixabay

Di bagian pertama dari seri ini, kami membuat satu blockchain. Sekarang kita akan membuat satu set dari mereka dan membuat mereka berbicara satu sama lain. Poin sebenarnya dari blockchain adalah sistem verifikasi terdistribusi. Anda dapat menambahkan blok dari node mana pun dan akhirnya mencapai node peer sehingga semua orang setuju seperti apa bentuk blockchain itu. Ini penting karena Anda tidak menginginkan satu pun sumber kebenaran dalam sistem terdistribusi.

Ada satu masalah yang langsung muncul: Setiap node adalah dua layanan, ditambah sebuah bus pesan MongoDB dan Kafka yang semuanya perlu berbicara satu sama lain. Tetapi saya ingin menguji dan mendemonstrasikan beberapa node pada satu Host (laptop saya). Saya telah menjalankan dengan penulisan Docker, jadi saya harus membuat satu file penulisan Docker untuk setiap node untuk membantu menjaga port tetap lurus.

Kami akan mengerjakan layanan node yang memungkinkan node bekerja satu sama lain. Ini akan mendapatkan input dari dua tempat, antarmuka yang tenang yang memungkinkan Anda untuk menambahkan dan membuat daftar node yang terhubung, dan bus pesan yang disediakan oleh Kafka yang memberi tahu layanan node tentang perubahan di blockchain lokal yang perlu disiarkan ke node peer.

Agar layanan dapat digunakan sebagai gambar, saya akan menggunakan plugin Google Jib maven. Ini adalah cara paling sederhana untuk membuat gambar dari maven build. Kami menambahkan yang berikut ini ke file pom dari setiap modul yang kami perlukan gambarnya:




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


Dalam kasus kami, kami dapat menggunakan nilai default untuk konfigurasi. Selanjutnya, Anda dapat berlari mvn clean install jib:build dan itu akan membuat gambar yang dapat Anda gunakan di file penulisan Docker Anda.

Mari kita ambil apa yang kita miliki sejauh ini dan memulai semuanya dengan file penulisan Docker lengkap bernama docker-compose-node1:

versi: '3.1'
layanan:
mongo:
gambar: mongo
mulai ulang: selalu
port:
- 27017
lingkungan Hidup:
MONGO_INITDB_ROOT_USERNAME: akar
MONGO_INITDB_ROOT_PASSWORD: contoh
mongo-ekspres:
gambar: mongo express
mulai ulang: selalu
port:
- 8081: 8081
lingkungan Hidup:
ME_CONFIG_MONGODB_ADMINUSERNAME: akar
ME_CONFIG_MONGODB_ADMINPASSWORD: contoh
penjaga kebun binatang:
gambar: confluentinc/cp-zookeeper:latest
port:
- 2181
lingkungan Hidup:
PENJAGA KEBUN BINATANG_CLIENT_PORT: 2181
kafka:
gambar: confluentinc/cp-kafka:terbaru
port:
- 9092
- 29092
tautan:
- penjaga kebun binatang
lingkungan Hidup:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: penjaga kebun binatang: 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
rantai blok:
gambar: rlkamradt/blockchain:1.0-SNAPSHOT
port:
- 8080: 8080
lingkungan Hidup:
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVER: kafka:29092

Anda akan melihat beberapa hal telah berubah. Pertama, saya menghapus port eksternal dari Kafka sehingga hanya dapat diakses di dalam jaringan penulisan. Kemudian saya telah menambahkan aplikasi blockchain dengan gambar yang dibuat oleh build. Akhirnya, saya mengganti beberapa properti pegas dengan variabel lingkungan sehingga dapat mengakses mongo dan Kafka dari dalam jaringan penulisan. Compose akan membuat entri DNS sehingga layanan yang dijalankan dari file penulisan yang sama dapat saling mengakses. Jalankan dengan perintah ini docker compose -f docker-compose-node1.yaml up -d dan pastikan Anda masih dapat menekan API blockchain dasar:

Sekarang aplikasi kita sudah aktif dan berjalan dan telah membuat blok genesis.

Sudah ada kode di layanan blockchain yang mengirim pesan ke Kafka setiap kali kita menambahkan blok atau menambahkan transaksi. Kami perlu membuat layanan baru yang akan membaca acara tersebut dan menyiarkannya ke daftar rekan. Mari kita turunkan apa yang telah kita jalankan untuk saat ini dan tambahkan layanan simpul sederhana yang akan mencatat pesan saat diterima. Kami akan membutuhkan modul baru dalam proyek — ini akan menjadi layanan Boot Musim Semi lain dan akan dapat berbicara dengan node eksternal.

Pertama, kita perlu mendefinisikan sebuah node, yang hanya berupa URL. ini dia Node:

@Data
@Pembangun
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(abaikanTidak diketahui = benar)
@Dokumen
simpul kelas publik {
@Indo
id String pribadi;
url String pribadi;
}

Kami menyimpannya dalam koleksi di MongoDB, jadi kami membutuhkan antarmuka repositori:

@Gudang
antarmuka publik NodeRepository meluas
ReactiveMongoRepository {
Mono findByUrl(String url);
}

Selanjutnya, kita perlu memiliki pengontrol untuk dapat melihat dan menambahkan Node benda:

@Slf4j
@RestController
@RequestMapping("/simpul")
kelas publik SimpleNodeController {
blockchain Blockchain akhir pribadi;
SimpleNodeService akhir pribadi simpleNodeService;

SimpleNodeController publik (Blockchain blockchain,
SimpleNodeService simpleNodeService) {
this.simpleNodeService = simpleNodeService;
ini.blockchain = blockchain;
}

@GetMapping(path = "peers", menghasilkan =
Tipe media.TEXT_EVENT_STREAM_VALUE)
Aliran getNode() {
kembalikan simpleNodeService.getPeers();
}

@PostMapping(path = "peers", menghasilkan =
Tipe media.APPLICATION_JSON_VALUE)
Mono addNode(@RequestBody Node node) {
kembalikan simpleNodeService.connectToPeer(node);
}

}

Kemudian, kita membutuhkan kelas layanan sederhana yang akan berinteraksi dengan MongoDB dan Kafka:

@Komponen
@Slf4j
SimpleNodeService kelas publik {
Node statis pribadi diri;
Host String pribadi terakhir;
pelabuhan int pribadi akhir;
rekan NodeRepository pribadi terakhir;
blockchain Blockchain pribadi terakhir;
ReactiveKafkaConsumerTemplate pribadi terakhir
Pesan> pemancar;

publik SimpleNodeService(@Value("${server.host}") String host,
@Value("${server.port}") Port string,
rantai blok blok,
rekan NodeRepository,
ReactiveKafkaConsumerTemplate pemancar) {
this.host = tuan rumah;
this.port = Bilangan bulat.parseInt(Pelabuhan);
ini.blockchain = blockchain;
this.emitor = emitor;
this.peers = rekan-rekan;
diri = simpul.pembangun()
.url("http://" + host + ":" + port)
.membangun();
emitor
.menerimaAutoAck()
.doOnNext(consumerRecord -> mencatat.info(
"received key={}, value={} from topic={}, offset={}",
konsumenRecord.key(),
konsumenRecord.nilai(),
konsumenRecord.topic(),
konsumenRecord.offset())
)
.map(ConsumerRecord::nilai)
.langganan(
m -> mencatat.info("pesan yang diterima {}", m),
e -> mencatat.error("kesalahan menerima pesan", e));
}

Fluks publik getPeer() {
kembali rekan.findAll();
}

Mono publik connectToPeer(Node simpul) {
kembali rekan-rekan.save(simpul);
}
}

Pesan apa pun yang diterima dari Kafka hanya akan dicatat dan kami tidak akan melakukan apa pun dengan node, selain menyimpan dan mencantumkannya.

Terakhir, kita perlu memberi tahu Spring Boot di mana ia dapat menemukan komponen dan repositori bersama. Kami dapat memberi anotasi pada kelas utama:

@Slf4j
@SpringBootAplikasin
@KomponenScan(Kelas Paket Dasar = {
net.kamradtfamily.blockchain.api.Blockchain.kelas,
net.kamradtfamily.blockchainnode.Application.class})
@AktifkanReaktifMongoRrepositori(basePackageClasses = {
net.kamradtfamily.blockchain.api.BlockRepository.class,
net.kamradtfamily.blockchainnode.NodeRepository.class})
Aplikasi kelas publik {
public static void main (String [] args) {
SpringApplication.run(Application.class, argumen);
coba {
Properti gitProps = Properti baru();
gitProps.memuat(
Aplikasi
.kelas
.getResourceAsStream("/git.properties"));
log.info("Properti Git:");
gitProps.entrySet().stream()
.forEach(es ->
log.info("{}: {}",
es.getKey(),
es.getValue()));
} tangkap (Pengecualian e) {
log.error("Error membaca Git Properties");
}
}
}

Spring perlu diberi tahu di mana harus memindai komponen dan repositori. Biasanya terlihat dalam paket tempat kelas utama berada tetapi dalam kasus kami, kami ingin berbagi komponen dari net.kamradtfamily.blockchain.api. Jadi saya menambahkan ComponentScan dan EnableReactiveMongoRepositories penjelasan. Saya juga menambahkan beberapa logging sehingga setiap kali dimulai kita akan tahu apa Git commit hash yang kita jalankan.

Untuk menjalankan semua ini, kita perlu memindahkan beberapa port. Untuk memiliki layanan baru dan layanan yang ada, kami harus memberikan masing-masing port eksternal yang unik. Mari tambahkan itu ke kami docker-compose-node1.yaml:

simpul rantai blok:
gambar: rlkamradt/blockchainnode:1.0-SNAPSHOT
port:
- 8080: 8082
lingkungan Hidup:
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVER: kafka:29092

Layanan MongoExpress sudah menggunakan port 8081, jadi kami akan mengeksposnya sebagai 8082. Sekarang buat gambar baru, tarik semuanya, dan jalankan semuanya:

mvn clean install jib: build
docker compose -f docker-compose-node1.yaml tarik
docker compose -f docker-compose-node1.yaml ke atas

Kemudian ketika Anda membuat transaksi dengan layanan blockchain, Anda akan melihat di log dari blockchainnode layanan bahwa pesan diterima. Anda juga akan dapat mencapai titik akhir http://localhost:8082/node/peers dan membuat dan daftar rekan-rekan.

Di sinilah segalanya menjadi rumit. Kami membutuhkan lebih dari satu node yang berjalan dan kami membutuhkan node untuk menanggapi pesan setelah transaksi atau penambahan blok. Kami juga membutuhkan node untuk berbicara satu sama lain selama start-up atau menambahkan node. Saya akan menyalin SimpleNodeService dan SimpleNodeController untuk NodeService dan NodeController. Saya akan meninggalkan kelas lama jika Anda melihat kode di GitHub dan ingin mengikuti, tetapi saya akan mengomentari Component dan RestController anotasi sehingga mereka tidak memulai saat runtime.

Saya akan menambahkan titik akhir tambahan ke NodeController untuk memungkinkan konfirmasi bahwa suatu transaksi telah berhasil masuk ke blok di semua node:

@GetMapping(path = "transactions/:transactionId/confirmations",
menghasilkan = MediaType.SEMUA_VALUE)
Mono dapatkanTransaksiDariNode(
@RequestParam("transactionId") String transaksiId) {
kembali layanan simpul
.getConfirmations(Panjang.Nilai dari(ID transaksi))
.map(b -> b.toString());
}

Ini berarti saya memerlukan seperangkat metode baru di in NodeService untuk mendapatkan konfirmasi dari semua node:

Mono publik getConfirmation(Node peer, transactionId panjang) {
String URL = rekan.getUrl()
+ "/blok/blok/transaksi/"
+ ID transaksi;
mencatat.info("Mendapatkan transaksi dari: {}", URL);
kembalikan klien
.Dapatkan()
.uri(URL)
.retrieve().bodyToMono(Block.class);
.onErrorContinue((t, o) -> Mono.kosong());
}

Mono getConfirmations(Id transaksi panjang) {
// Dapatkan hitungan rekan dengan konfirmasi bahwa transaksi itu ada
kembalikan blockchain
.findTransactionInChain(transactionId, blockchain
.getAllBlocks())
.zipDengan(peers.findAll()
.flatMap(peer -> getConfirmation(peer,
ID transaksi)))
.menghitung();
}

Ini akan mengembalikan jumlah node yang memiliki transaksi ini dalam satu blok. Tapi pertama-tama, saya perlu membuat titik akhir baru di BlockController, yang akan melaporkan jika transaksi berada di blok di blockchain.

@GetMapping(path = "blok/transaksi/{transactionId}", 
menghasilkan = MediaType.APPLICATION_JSON_VALUE)
Mono publik dapatkanTransaksi(
@PathVariable("transactionId") panjang transactionId) {
kembalikan blockchain
.findTransactionInChain(transactionId,
blockchain.getAllBlocks())
.last() // anggap hanya ada satu
.switchIfEmpty(Mono.kesalahan(ResponsStatusException baru (
Status Http.TIDAK DITEMUKAN,
"Transaksi Tidak Ditemukan di Blockchain")));

Untungnya, kami sudah memiliki metode findTransactionInChain yang akan mengembalikan blok tempat transaksi ditemukan.

Selanjutnya, kita harus membalas pesan dari Kafka. Kami akan menambahkan messageHandler metode yang akan menyiarkan pesan ke semua node peer:

Mono publik messageHandler(Pesan m) {
if("addedBlock".equals(m.getMessage())) {
kembali rekan-rekan.findAll()
.flatMap(p -> sendLatestBlock(p, m.getBlock()))
.switchIfEmpty(Mono.just(m.getBlock()))
.terakhir();
} else if("addedTransaction".equals(m.getMessage())) {
kembali rekan-rekan.findAll()
.flatMap(p -> sendTransaction(p, m.getTransaction()))
.switchIfEmpty(Mono.just(m.getTransaction()))
.terakhir();
} else if("getBlocks".equals(m.getMessage())) {
kembali rekan-rekan.findAll()
.flatMap(p -> getBlocks(p))
.switchIfEmpty(blockchain.getLastBlock())
.terakhir();
} Else {
log.error("pesan tidak diketahui {}", m);
kembalikan Mono.kosong();
}
}

Ini membutuhkan dua metode baru untuk membuat permintaan ke node lain:

Mono publik sendLatestBlock(Node peer, 
Blok blok) {
String URL = peer.getUrl() + "/block/blocks/latest";
mencatat.info("Memposting blok terbaru ke: {}", URL);
kembalikan klien
.taruh()
.uri(URL)
.body(blok, Blok.kelas)
.bertukar();
}

Mono publik sendTransaction(Node peer,
transaksi transaksi) {
String URL = peer.getUrl() + "/transaksi";
mencatat.info("Mengirim transaksi '{}' ke: {}", transaksi, URL);
kembalikan klien
.pos()
.uri(URL)
.body(transaksi, Transaction.class)
.bertukar();
}

Kami sudah memiliki POST untuk /transaction titik akhir, tetapi kita perlu menambahkan PUT ke /block/blocks/latest titik akhir.

@PutMapping(path = "/blok/blok/terbaru", 
menghasilkan = MediaType.APPLICATION_JSON_VALUE)
Mono publik cekDiterimaBlok(
@RequestBody Block diterimaBlock) {
kembalikan blockchain.checkReceivedBlock(receivedBlock);
}

Ini membutuhkan metode baru dalam Blockchain layanan.

Mono publik checkReceivedBlock(Blokir diterimaBlock) {
kembali getLastBlock()
.filter(b -> b.getIndex() < diterimaBlock.getIndex())
.flatMap(b -> {
log.info(
"Blockchain mungkin tertinggal. Kami mendapat: {}, Peer mendapat: {}",
b.getIndex(),
menerimaBlock.getIndex());
if (b.getHash().equals(receivedBlock.getPviousHash())) {
log.info("Menambahkan blok yang diterima ke rantai kami");
kembalikan addBlock(receivedBlock, true);
} Else {
log.info("Meminta rantai dari rekan-rekan kami");
emitor.send(TOPIC, Message.builder()
.type("kosong")
.message("getBlocks")
.membangun())
.langganan();
kembalikan Mono.kosong();
}
});
}

Anda dapat melihat bagaimana lapisan simpul berbicara dengan simpul lain melalui API publik. Namun ada satu masalah. Setiap node diwakili oleh satu URL, tetapi kita harus berbicara dengan dua layanan terpisah: layanan blok dan layanan simpul. Saya akan membuat ingress sederhana dengan instance Nginx. Dengan begitu kita dapat berbicara dengan kedua layanan (dan lebih banyak lagi nanti) dengan satu URL. Anda dapat melihat kode di GitHub untuk detail tentang konfigurasi Nginx dan menambahkan semua layanan ke docker-compose-node1.yaml.

Memulai semuanya, semua titik akhir masih berfungsi dan saya dapat melihat di log komunikasi antara layanan blockchain dan layanan node di bus Kafka. Sekarang saatnya membuat simpul kedua. Salin docker-compose-node1.yaml untuk docker-compose-node2.yaml dan alihkan port eksternal layanan Nginx dari 8080 ke 8081 untuk mencapai node 1 di port 8080 dan node 2 di port 8081. Saya juga akan membuat skrip kecil bernama startnode1 untuk memulai setiap layanan secara berurutan dan mengeluarkan log dari layanan simpul:

buruh pelabuhan menulis -p node1 -f docker-compose-node1.yaml naik -d mongo
buruh pelabuhan menulis -p node1 -f docker-compose-node1.yaml naik -d penjaga kebun binatang
buruh pelabuhan menulis -p node1 -f docker-compose-node1.yaml naik -d mongo-express
penulisan docker -p node1 -f docker-compose-node1.yaml up -d kafka
buruh pelabuhan menulis -p node1 -f docker-compose-node1.yaml naik -d blockchain
docker compose -p node1 -f docker-compose-node1.yaml di blockchainnode

Karena baris terakhir tidak memiliki -d flag, ini akan menampilkan log sampai Ctrl-C menghentikannya. saya menggunakan -p node1 flag sehingga saya dapat membuat instance layanan yang terpisah. Selanjutnya, salin ke file bernama startnode2, tetapi ganti file penulisan Docker untuk memulai node 2 dan -p bendera ke node2. Jangan lupa untuk mengatur flag yang dapat dieksekusi pada masing-masing:

chmod +x startnode1
chmod +x startnode2

Ada satu perubahan terakhir. Anggota sendiri dari layanan simpul harus memiliki URL seperti yang terlihat oleh layanan lain, jadi menggunakan Host lokal tidak akan berhasil. Saya menyiapkan properti Spring di application.properties:

server.sendiri: http://localhost:8080

Lalu saya menimpanya di docker-compose-node1.yaml yang sekarang terlihat seperti ini:

versi: '3.1'
layanan:
mongo:
gambar: mongo
mulai ulang: selalu
port:
- 27017
lingkungan Hidup:
MONGO_INITDB_ROOT_USERNAME: akar
MONGO_INITDB_ROOT_PASSWORD: contoh
penjaga kebun binatang:
gambar: confluentinc/cp-zookeeper:latest
port:
- 2181
lingkungan Hidup:
PENJAGA KEBUN BINATANG_CLIENT_PORT: 2181
kafka:
gambar: confluentinc/cp-kafka:terbaru
port:
- 9092
- 29092
tautan:
- penjaga kebun binatang
lingkungan Hidup:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: penjaga kebun binatang: 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
rantai blok:
gambar: rlkamradt/blockchain:1.0-SNAPSHOT
port:
- 8080
lingkungan Hidup:
SERVER_SENDIRI: http://192.168.0.174:8080
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVER: kafka:29092
simpul rantai blok:
gambar: rlkamradt/blockchainnode:1.0-SNAPSHOT
port:
- 8080
lingkungan Hidup:
SERVER_SENDIRI: http://192.168.0.174:8080
MONGO_HOST: mongo
SPRING_KAFKA_BOOTSTRAP-SERVER: kafka:29092
nginx:
gambar: nginx:terbaru
volume:
- ./nginx.conf:/etc/nginx/nginx.conf
port:
- 8080: 80

Grafik docker-compose-node2.yaml juga mengubah port ke 8081 untuk keduanya SERVER_MYSELF dan nginx.ports nilai.

Mulai kedua instance. Saat keduanya berjalan, Anda dapat menghubungkan satu ke yang lain:

Sekarang Anda dapat membuat transaksi dan menambang blok, seperti yang ditunjukkan di Artikel sebelumnya, tetapi Anda dapat membuat daftar transaksi dan blok di kedua node. Protokol peer-to-peer memastikan bahwa kedua node memiliki data yang sama.

Saya tidak akan mengatakan itu sempurna pada saat ini. Ada banyak urutan berbeda yang perlu diuji untuk memastikan bahwa tidak peduli bagaimana hal itu dilakukan, blockchain dalam berbagai contoh tetap sama. Tapi artikel ini sudah cukup panjang dan saya yakin Anda tidak ingin membaca tentang saya men-debug web ini!

Terima kasih telah membaca artikel yang agak panjang ini, saya telah mencoba menyingkatnya untuk membuatnya sesingkat mungkin tetapi ini adalah topik yang sangat rumit.

Saya pikir artikel berikutnya dalam seri ini akan sedikit lebih sederhana. Kami akan membahas bagian terakhir dari teka-teki: penambang dan pengguna.

Semua kode untuk artikel ini dapat ditemukan di sini:

Artikel sebelumnya dalam seri ini:

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

Stempel Waktu:

Lebih dari Medium