ساخت مجموعه ای از بلاک چین با استفاده از هوش داده پلاتوبلاک چین جاوا. جستجوی عمودی Ai.

ساخت مجموعه ای از بلاک چین با استفاده از جاوا

اعتماد از طریق شبکه های همتا به همتا

تار عنکبوت
تصویر کلودیا وولسن از جانب Pixabay

در قسمت اول این مجموعه، یک بلاک چین واحد ساختیم. حالا می‌خواهیم مجموعه‌ای از آنها بسازیم و آنها را وادار کنیم که با یکدیگر صحبت کنند. نقطه واقعی بلاک چین یک سیستم تایید توزیع شده است. می‌توانید از هر گره‌ای بلوک‌ها را اضافه کنید و در نهایت به گره‌های همتا می‌رسد تا همه در مورد ظاهر بلاک چین توافق کنند. این مهم است زیرا شما یک منبع حقیقت واحد در یک سیستم توزیع شده نمی خواهید.

یک مشکل فوراً پیش می آید: هر گره دارای دو سرویس است، به علاوه یک MongoDB و یک گذرگاه پیام کافکا که همه باید با یکدیگر صحبت کنند. اما من می خواهم چندین گره را روی یک هاست (لپ تاپ من) آزمایش و نشان دهم. من با Docker compose اجرا می‌کردم، بنابراین باید برای هر گره یک فایل نوشتن Docker بسازم تا به ثابت نگه داشتن پورت‌ها کمک کند.

ما روی یک سرویس گره کار خواهیم کرد که به گره ها اجازه می دهد با یکدیگر کار کنند. این ورودی از دو مکان دریافت می‌کند، یک رابط آرام که به شما امکان می‌دهد گره‌های متصل را اضافه کرده و فهرست کنید، و یک گذرگاه پیام ارائه‌شده توسط کافکا که سرویس گره‌ها را از تغییرات در بلاک چین محلی که باید برای گره‌های همتا پخش شوند، مطلع می‌کند.

برای اینکه از خدمات به عنوان تصویر استفاده شود، از افزونه Google Jib maven استفاده خواهم کرد. این ساده ترین راه برای ایجاد یک تصویر از ساخت maven است. موارد زیر را به فایل pom هر ماژول اضافه می کنیم که برای آن به یک تصویر نیاز داریم:




com.google.cloud.tools
پلاگین jib-maven
2.7.1


در مورد ما، می توانیم از مقادیر پیش فرض برای پیکربندی استفاده کنیم. بعد، شما می توانید اجرا کنید mvn clean install jib:build و تصویری را ایجاد می کند که می توانید در فایل نوشتن Docker خود استفاده کنید.

بیایید آنچه را که تا کنون داشته‌ایم در نظر بگیریم و همه چیز را با یک فایل کامپوزیسی جامع Docker به نام شروع کنیم. docker-compose-node1:

نسخه: '3.1'
خدمات:
مونگو:
تصویر: مونگو
راه اندازی مجدد: همیشه
بنادر:
لغایت ۱۳
محیط:
MONGO_INITDB_ROOT_USERNAME: ریشه
MONGO_INITDB_ROOT_PASSWORD: مثال
mongo-express:
تصویر: mongo-express
راه اندازی مجدد: همیشه
بنادر:
- 8081: 8081
محیط:
ME_CONFIG_MONGODB_ADMINUSERNAME: ریشه
ME_CONFIG_MONGODB_ADMINPASSWORD: مثال
نگهبان باغ وحش:
تصویر: confluentinc/cp-zookeeper:جدیدترین
بنادر:
لغایت ۱۳
محیط:
ZOOKEEPER_CLIENT_PORT: 2181
کافکا:
تصویر: confluentinc/cp-kafka:جدیدترین
بنادر:
لغایت ۱۳
لغایت ۱۳
پیوندها:
- نگهبان باغ وحش
محیط:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper: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
بلاک چین:
تصویر: rlkamradt/blockchain:1.0-SNAPSHOT
بنادر:
- 8080: 8080
محیط:
MONGO_HOST: مونگو
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092

متوجه خواهید شد که چند چیز تغییر کرده است. ابتدا پورت خارجی را از کافکا حذف کردم تا فقط در داخل شبکه کامپوزی قابل دسترسی باشد. سپس برنامه بلاک چین را با تصویر ایجاد شده توسط بیلد اضافه کردم. در نهایت، من چند ویژگی فنری را با متغیر محیطی جایگزین کردم تا از داخل شبکه compose به mongo و کافکا دسترسی پیدا کند. Compose ورودی‌های DNS را ایجاد می‌کند تا سرویس‌های اجرا شده از یک فایل compose بتوانند به یکدیگر دسترسی داشته باشند. با این دستور آن را اجرا کنید docker compose -f docker-compose-node1.yaml up -d و مطمئن شوید که همچنان می‌توانید به API اصلی بلاک چین ضربه بزنید:

اکنون برنامه ما فعال است و بلوک genesis را ایجاد کرده است.

در حال حاضر کدی در سرویس بلاک چین وجود دارد که هر زمان که یک بلوک یا تراکنش اضافه می کنیم، پیامی به کافکا می فرستد. ما باید یک سرویس جدید ایجاد کنیم که آن رویدادها را بخواند و آنها را برای لیستی از همتایان پخش کند. بیایید آنچه را که در حال حاضر اجرا کرده‌ایم پایین بیاوریم و یک سرویس گره ساده اضافه کنیم که پیامی را هنگام دریافت ثبت می‌کند. ما به یک ماژول جدید در پروژه نیاز داریم - این یکی دیگر از سرویس‌های Spring Boot خواهد بود و می‌تواند با گره‌های خارجی صحبت کند.

ابتدا باید یک گره را تعریف کنیم که فقط یک URL است. اینجاست Node:

@داده ها
@سازنده
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties (ناشناس = درست)
@سند
گره کلاس عمومی {
@شناسه
شناسه رشته خصوصی.
URL رشته خصوصی.
}

ما آن را در مجموعه ای در MongoDB ذخیره می کنیم، بنابراین به یک رابط مخزن نیاز داریم:

@Repository
رابط عمومی NodeRepository گسترش می یابد
ReactiveMongoRepository {
مونونوکلئوز findByUrl(string url);
}

در مرحله بعد، باید یک کنترلر داشته باشیم تا بتوانیم آن را ببینیم و اضافه کنیم Node اشیاء:

@Slf4j
@RestController
@RequestMapping ("/node")
کلاس عمومی SimpleNodeController {
بلاک چین نهایی خصوصی؛
خصوصی نهایی SimpleNodeService simpleNodeService.

عمومی SimpleNodeController (بلاک چین،
SimpleNodeService simpleNodeService) {
this.simpleNodeService = simpleNodeService;
this.blockchain = بلاک چین;
}

@GetMapping( مسیر = "همتایان"، = تولید می کند
نوع رسانه.TEXT_EVENT_STREAM_VALUE)
شار getNodes() {
بازگشت simpleNodeService.getPeers();
}

@PostMapping(مسیر = "همتایان"، = تولید می کند
نوع رسانه.APPLICATION_JSON_VALUE)
مونونوکلئوز addNode(@RequestBody Node node) {
بازگشت simpleNodeService.connectToPeer(node);
}

}

سپس، ما به یک کلاس سرویس ساده نیاز داریم که با MongoDB و Kafka تعامل داشته باشد:

@جزء
@Slf4j
کلاس عمومی SimpleNodeService {
گره استاتیک خصوصی yo mismo;
میزبان رشته خصوصی نهایی؛
درگاه خصوصی نهایی int;
همتایان خصوصی NodeRepository نهایی؛
بلاک چین خصوصی نهایی؛
ReactiveKafkaConsumerTemplate نهایی خصوصی
Message> Emitter;

عمومی SimpleNodeService(@Value("${server.host}") میزبان رشته،
@Value("${server.port}") پورت رشته،
بلاک چین،
همتایان NodeRepository،
ReactiveKafkaConsumerTemplate ساطع کننده) {
this.host = میزبان;
this.port = عدد صحیح.parseInt(بندر)؛
this.blockchain = بلاک چین;
this.emitter = ساطع کننده;
this.peers = همتایان;
yo mismo = گرهسازنده()
.url("http://" + میزبان + ":" + پورت)
.ساختن()؛
امیتر
.receiveAutoAck()
.doOnNext(consumerRecord -> ورود به سیستم.info(
"received key={}, value={} from topic={}, offset={}",
customersRecord.key()،
customersRecord.value()،
customersRecord.topic()،
customersRecord.offset())
)
.map(ConsumerRecord::value)
.اشتراک در(
m -> ورود به سیستم.info("پیام دریافت شده {}"، m)
e -> ورود به سیستم.error ("خطا در دریافت پیام"، e));
}

شار عمومی getPeers() {
بازگشت peers.findAll();
}

مونو عمومی connectToPeer (گره گره) {
بازگشت peers.save(node);
}
}

هر پیامی که از کافکا دریافت می‌شود به سادگی ثبت می‌شود و ما در واقع هیچ کاری با گره‌ها انجام نمی‌دهیم، به غیر از ذخیره و فهرست کردن آنها.

در نهایت، باید به Spring Boot بگوییم که کجا می تواند اجزا و مخازن مشترک را پیدا کند. می توانیم کلاس اصلی را حاشیه نویسی کنیم:

@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})
برنامه کلاس عمومی {
public static void main(string [] args) {
SpringApplication.run(Application.class، args);
تلاش كردن {
مشخصات gitProps = new Properties();
gitProps.load(
کاربرد
.کلاس
.getResourceAsStream("/git.properties"));
log.info ("ویژگی های Git:");
gitProps.entrySet().stream()
.forEach(es ->
log.info("{}: {}",
es.getKey()،
es.getValue()));
} catch(Exception e) {
log.error ("خطا در خواندن ویژگی های Git");
}
}
}

باید به اسپرینگ گفته شود که کجا برای کامپوننت ها و مخازن اسکن شود. معمولاً در بسته ای به نظر می رسد که کلاس اصلی در آن قرار دارد، اما در مورد ما می خواستیم اجزایی را از آن به اشتراک بگذاریم net.kamradtfamily.blockchain.api. بنابراین من اضافه کردم ComponentScan و EnableReactiveMongoRepositories حاشیه نویسی من همچنین مقداری ورود به سیستم را اضافه کردم، بنابراین هر زمان که آن را راه اندازی کرد، ما بدانیم که Git چه هش commit را اجرا می کنیم.

برای اجرای همه اینها، باید چند پورت را جابجا کنیم. برای داشتن سرویس جدید و سرویس موجود، باید به هر یک از آنها پورت های خارجی منحصربفردی بدهیم. بیایید آن را به ما اضافه کنیم docker-compose-node1.yaml:

blockchainnode:
تصویر: rlkamradt/blockchainnode:1.0-SNAPSHOT
بنادر:
- 8080: 8082
محیط:
MONGO_HOST: مونگو
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092

سرویس MongoExpress در حال حاضر پورت 8081 را دریافت کرده است، بنابراین ما آن را به عنوان 8082 در معرض دید قرار می دهیم. اکنون تصاویر جدید را بسازید، آنها را بکشید و همه آنها را اجرا کنید:

mvn clean install jib:build
docker compose -f docker-compose-node1.yaml کشش
docker compose -f docker-compose-node1.yaml بالا

سپس هنگامی که یک تراکنش با سرویس بلاک چین ایجاد می کنید، در لاگ های آن خواهید دید blockchainnode سرویسی که پیامی دریافت شد. شما همچنین می توانید به نقاط پایانی ضربه بزنید http://localhost:8082/node/peers و همتاها را ایجاد و فهرست کنید.

اینجاست که همه چیز پیچیده می شود. ما به بیش از یک گره نیاز داریم که در حال اجرا باشد و به گره ها نیاز داریم تا پس از تراکنش یا اضافه کردن بلوک به پیام ها پاسخ دهند. ما همچنین به گره ها نیاز داریم تا در هنگام راه اندازی یا اضافه کردن گره ها با یکدیگر صحبت کنند. من قصد دارم کپی کنم SimpleNodeService و SimpleNodeController به NodeService و NodeController. در صورتی که به کد GitHub نگاه می‌کنید و می‌خواهید آن را دنبال کنید، کلاس‌های قدیمی را ترک می‌کنم، اما می‌خواهم در مورد آن نظر بدهم. Component و RestController حاشیه نویسی تا در زمان اجرا شروع نشوند.

من قصد دارم یک نقطه پایانی اضافی به آن اضافه کنم NodeController برای تأیید اینکه یک تراکنش آن را به یک بلوک در تمام گره ها تبدیل کرده است:

@GetMapping(path = "transactions/:transactionId/confirmations",
تولید می کند = MediaType.ALL_VALUE)
مونونوکلئوز getTransactionFromNode(
@RequestParam("transactionId") شناسه تراکنش رشته ای) {
nodeService را برگرداند
.getConfirmations(Long.ارزش(شناسه تراکنش))
.map(b -> b.toString());
}

این به این معنی است که من به مجموعه ای از روش های جدید نیاز دارم NodeService برای دریافت تاییدیه از همه گره ها:

مونو عمومی getConfirmation (همتا گره، شناسه تراکنش طولانی) {
URL رشته = peer.getUrl()
+ "/block/blocks/transactions/"
+ شناسه تراکنش؛
ورود به سیستم.info("دریافت تراکنش ها از: {}", URL);
مشتری برگشت
.گرفتن()
uri (URL)
.retrieve().bodyToMono(Block.class);
.onErrorContinue((t, o) -> Mono.خالی())
}

مونونوکلئوز getConfirmations (شناسه تراکنش طولانی) {
// تعداد همتایان را با تأیید وجود تراکنش دریافت کنید
بازگشت بلاک چین
.findTransactionInChain(transactionId، blockchain
.getAllBlocks())
zipWith(peers.findAll()
flatMap(peer -> getConfirmation(peer,
شناسه تراکنش)))
.شمردن()؛
}

با این کار تعداد گره هایی که این تراکنش را در یک بلوک دارند برمی گرداند. اما ابتدا باید یک نقطه پایانی جدید در آن ایجاد کنم BlockController، یکی که اگر تراکنش در یک بلوک در بلاک چین باشد گزارش می دهد.

@GetMapping(path = "blocks/transaction/{transactionId}"، 
تولید می کند = MediaType.APPLICATION_JSON_VALUE)
مونو عمومی getTransaction(
@PathVariable("transactionId") LongtransactionId) {
بازگشت بلاک چین
.findTransactionInChain(transactionId,
blockchain.getAllBlocks())
.last() // فرض کنید فقط یکی وجود دارد
.switchIfEmpty(Mono.خطا(ResponseStatusException جدید(
HttpStatus.پیدا نشد,
"تراکنش در بلاک چین یافت نشد")))؛

خوشبختانه، ما قبلاً یک روش داریم findTransactionInChain که بلوکی را که تراکنش در آن یافت می شود برمی گرداند.

بعد، باید به پیام های کافکا پاسخ دهیم. یک را اضافه می کنیم messageHandler روشی که پیام ها را به تمام گره های همتا پخش می کند:

مونو عمومی messageHandler(Message m) {
if("addedBlock".equals(m.getMessage())) {
بازگشت peers.findAll()
flatMap(p -> sendLatestBlock(p, m.getBlock()))
.switchIfEmpty(Mono.just(m.getBlock()))
.آخر()؛
} else if("addedTransaction".equals(m.getMessage())) {
بازگشت peers.findAll()
flatMap(p -> sendTransaction(p, m.getTransaction()))
.switchIfEmpty(Mono.just(m.getTransaction()))
.آخر()؛
} else if("getBlocks".equals(m.getMessage())) {
بازگشت peers.findAll()
flatMap(p -> getBlocks(p))
.switchIfEmpty(blockchain.getLastBlock())
.آخر()؛
} دیگری {
log.error("پیام ناشناس {}"، m);
return Mono.empty();
}
}

این به دو روش جدید برای درخواست به گره های دیگر نیاز دارد:

مونو عمومی sendLatestBlock(همتا گره، 
بلوک بلوک) {
URL رشته = peer.getUrl() + "/block/blocks/latest";
ورود به سیستم.info("پست آخرین بلوک به: {}", URL);
مشتری برگشت
.قرار دادن()
uri (URL)
.body (block، Block.class)
.تبادل()؛
}

مونو عمومی sendTransaction(همتا گره،
معامله تراکنش) {
URL رشته = peer.getUrl() + "/transaction";
ورود به سیستم.info("ارسال تراکنش "{}" به: {}", تراکنش، URL);
مشتری برگشت
.پست()
uri (URL)
.body(transaction، Transaction.class)
.تبادل()؛
}

ما قبلاً POST را در اختیار داریم /transaction نقطه پایانی، اما باید PUT را به آن اضافه کنیم /block/blocks/latest نقطه پایانی

@PutMapping(path = "/block/blocks/latest", 
تولید می کند = MediaType.APPLICATION_JSON_VALUE)
مونو عمومی checkReceivedBlock(
@RequestBody Block receiveBlock) {
بازگشت blockchain.checkReceivedBlock(receivedBlock);
}

این نیاز به یک روش جدید در Blockchain خدمات.

مونو عمومی checkReceivedBlock(Block receiveBlock) {
بازگشت getLastBlock()
.filter(b -> b.getIndex() < receiveBlock.getIndex())
flatMap(b -> {
log.info(
"بلاکچین احتمالاً پشت سر گذاشته است. ما دریافتیم: {}، Peer got: {}"،
b.getIndex()،
receiveBlock.getIndex());
if (b.getHash().equals(receivedBlock.getPreviousHash())) {
log.info ("الحاق بلوک دریافتی به زنجیره ما");
بازگشت addBlock (receivedBlock، true)؛
} دیگری {
log.info ("جستجوی زنجیره ای از همتایان ما");
emitter.send(TOPIC, Message.builder()
.type ("خالی")
.message ("getBlocks")
.ساختن())
.اشتراک در()؛
return Mono.empty();
}
})؛
}

می توانید ببینید که چگونه لایه گره از طریق API عمومی با گره های دیگر صحبت می کند. هر چند مشکلی وجود دارد. هر گره با یک URL نشان داده می شود، اما ما باید با دو سرویس جداگانه صحبت کنیم: سرویس بلوک و سرویس گره. من قصد دارم یک ورود ساده با یک نمونه Nginx انجام دهم. به این ترتیب ما می توانیم با هر دو سرویس (و بعداً بیشتر) با یک URL واحد صحبت کنیم. برای جزئیات بیشتر در مورد پیکربندی Nginx و افزودن تمام خدمات به GitHub می توانید به کد موجود در GitHub نگاه کنید. docker-compose-node1.yaml.

با شروع همه چیز، همه نقاط پایانی هنوز کار می کنند و من می توانم در گزارش ها ارتباط بین سرویس بلاک چین و سرویس گره در اتوبوس کافکا را ببینم. اکنون زمان ایجاد یک گره دوم است. را کپی کنید docker-compose-node1.yaml به docker-compose-node2.yaml و پورت خارجی سرویس Nginx را از 8080 به 8081 تغییر دهید تا به نود 1 در پورت 8080 و نود 2 در پورت 8081 برسید. همچنین قصد دارم یک اسکریپت کوچک به نام ایجاد کنم. startnode1 برای شروع هر سرویس به ترتیب و خروجی گزارش ها از سرویس گره:

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

چون خط آخر الف ندارد -d پرچم، گزارش‌ها را نمایش می‌دهد تا زمانی که Ctrl-C آن را متوقف کند. من استفاده می کنم -p node1 پرچم گذاری کنید تا بتوانم نمونه های جداگانه ای از خدمات ایجاد کنم. سپس در فایلی به نام کپی کنید startnode2، اما برای راه اندازی گره 2 و فایل Docker compose را جایگزین کنید -p پرچم به node2. فراموش نکنید که پرچم اجرایی را روی هر کدام تنظیم کنید:

chmod +x startnode1
chmod +x startnode2

یک تغییر نهایی وجود دارد. عضو خودم سرویس گره باید URL را همانطور که توسط سرویس های دیگر دیده می شود داشته باشد، بنابراین استفاده از میزبان محلی این کار را نمی کند. من یک ویژگی Spring را در application.properties تنظیم کردم:

server.myself: http://localhost:8080

سپس آن را در docker-compose-node1.yaml لغو می کنم که اکنون به شکل زیر است:

نسخه: '3.1'
خدمات:
مونگو:
تصویر: مونگو
راه اندازی مجدد: همیشه
بنادر:
لغایت ۱۳
محیط:
MONGO_INITDB_ROOT_USERNAME: ریشه
MONGO_INITDB_ROOT_PASSWORD: مثال
نگهبان باغ وحش:
تصویر: confluentinc/cp-zookeeper:جدیدترین
بنادر:
لغایت ۱۳
محیط:
ZOOKEEPER_CLIENT_PORT: 2181
کافکا:
تصویر: confluentinc/cp-kafka:جدیدترین
بنادر:
لغایت ۱۳
لغایت ۱۳
پیوندها:
- نگهبان باغ وحش
محیط:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper: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
بلاک چین:
تصویر: rlkamradt/blockchain:1.0-SNAPSHOT
بنادر:
لغایت ۱۳
محیط:
SERVER_MYSELF: http://192.168.0.174:8080
MONGO_HOST: مونگو
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092
blockchainnode:
تصویر: rlkamradt/blockchainnode:1.0-SNAPSHOT
بنادر:
لغایت ۱۳
محیط:
SERVER_MYSELF: http://192.168.0.174:8080
MONGO_HOST: مونگو
SPRING_KAFKA_BOOTSTRAP-SERVERS: kafka:29092
nginx:
تصویر: nginx: آخرین
حجم:
- ./nginx.conf:/etc/nginx/nginx.conf
بنادر:
- 8080: 80

La docker-compose-node2.yaml همچنین پورت را برای هر دو به 8081 تغییر می دهد SERVER_MYSELF و nginx.ports ارزش.

هر دو نمونه را راه اندازی کنید. وقتی هر دو در حال اجرا هستند، می توانید یکی را به دیگری متصل کنید:

اکنون می توانید تراکنش ها و بلوک های استخراج را ایجاد کنید، همانطور که در نشان داده شده است مقاله قبلی، اما می توانید تراکنش ها و بلوک ها را در هر دو گره لیست کنید. پروتکل peer-to-peer تضمین می کند که هر دو گره داده های یکسانی دارند.

من نمی گویم در این مرحله کامل است. توالی های مختلفی وجود دارد که باید آزمایش شوند تا اطمینان حاصل شود که صرف نظر از اینکه کارها چگونه انجام می شوند، بلاک چین در موارد مختلف یکسان باقی می ماند. اما این مقاله در حال حاضر به اندازه کافی طولانی است و من مطمئن هستم که شما نمی خواهید در مورد من که این وب را اشکال زدایی می کنم بخوانید!

با تشکر از شما برای خواندن این مقاله نسبتا طولانی، من سعی کرده ام آن را فشرده کنم تا آن را تا حد امکان خلاصه کنم، اما موضوع بسیار پیچیده ای است.

فکر می کنم مقاله بعدی این مجموعه کمی ساده تر باشد. ما در مورد بخش پایانی پازل بحث خواهیم کرد: ماینرها و کاربران.

تمام کدهای این مقاله را می توانید در اینجا بیابید:

مقاله قبلی این مجموعه:

منبع: https://betterprogramming.pub/build-a-set-of-blockchains-using-java-d99cd866931b?source=rss——-8—————–کریپتوکارنسی

تمبر زمان:

بیشتر از متوسط