مرحبا يا هبر!
بالنسبة لأولئك المهتمين بموضوع blockchain ، ليس سراً أنه بالإضافة إلى القيود العامة ، مثل Ethereum ، و Bitcoin ، و Zcash ، وما إلى ذلك ، هناك أيضًا "إخوانهم" للمؤسسات (الخاصة) والذين يكونون في بعض الأحيان أفضل من الشبكات العامة ، لكن في شيء يخسرونه. من بين أكثر الشبكات المعروفة ، أعتقد أنه يمكنك تسمية Quorum (بائع - JP Morgan Chase ) ، و Pantheon (بائع - PegaSys ) و Hyperledger (تديره مؤسسة Linux ). على الرغم من أن هناك الكثير من القرارات العامة ، إلا أن الشركات تهتم بشكل متزايد بالحصول على قيود خاصة لأنها قادرة على توفير المستوى الضروري من الخصوصية والمعاملات تكون أسرع وهلم جرا. الاختلافات بين القيود الخاصة والعامة ، وكذلك مزاياها وعيوبها ، ليست موضوع هذا المقال. إذا كنت مهتمًا بالقراءة حول هذا الموضوع ، فهناك ، على سبيل المثال ، مقالة حول متوسطة .
في هذه المقالة ، أود أن أخبرك كيف يمكنك استخدام Quorum blockchain لتطوير تطبيقاتك مع دعم المعاملات الخاصة والعامة. لإظهار القدرات ، سنقوم بكتابة تطبيق Java / Spring صغير يقبل طلبات نشر (نشر) العقود الذكية وتنفيذ المعاملات وقراءة البيانات من عقد ذكي. في الواقع ، إليك مكدس التقنية الذي سيتم استخدامه في المقالة:
Quorum هو مشروع برمز مفتوح المصدر على GitHub ، والغرض منه هو توفير blockchain من شأنه أن يجعل من الممكن تنفيذ المعاملات ليس فقط للجمهور ولكن أيضًا في الوضع الخاص. من وجهة نظر تقنية ، فإن Quorum هي ترقية Ethereum ؛ كما أن لديها عميل Geth المعدل الخاص بها لتتمكن من القيام بالمعاملات الخاصة.
إضافة هامة هي الخدمات المحصورة ، المسؤولة عن تخزين وتشفير وتوزيع المعاملات الخاصة فيما بينها. الآن هناك 2 مثل هذه الخدمات الجيب :
- كوكبة - مكتوبة في هاسكل ، النسخة الأولى من الجيب ، لكنها الآن لم تعد تتطور ، وعلى الأرجح في المستقبل سيتم التخلي عنها لصالح واحدة جديدة ؛
- Tessera - خدمة جديدة ، مكتوبة بلغة Java ، بدعم من المطورين من JP Morgan Chase ، لديها خيارات أكثر للتكامل مع قاعدة البيانات وإدارة المعلومات الحساسة (على سبيل المثال ، هناك خيار للتكامل مع HashiCorp Vault لإدارة الأسرار).
بالنسبة للمعاملات ، من وجهة نظر واجهة Ethereum العادية ، لم يتغير الكثير (وهذا أمر جيد). لإرسال معاملة خاصة ، بالإضافة إلى المعلومات المعتادة حول المعاملة ، تحتاج أيضًا إلى تحديد معلمة privateFor - هذه هي مجموعة الخطوط ، وهذه الخطوط هي مفاتيح عامة لعقدة الجيب . باستخدام هذه المفاتيح ، يتم تشفير معاملات الحمولة النافعة ويتم توزيع الراتب بين عقد Tessera داخل blockchain.
للحصول على معرفة أعمق مع Quorum ، وكيفية عمله ، وكيفية رفع شبكة blockchain ، يمكنك العثور عليها على الموقع الرسمي (رابط إلى الوثائق ، وكذلك رابط إلى البرنامج التعليمي حول كيفية إطلاق blockchain اختبار ، سأترك في نهاية المقال).
تطوير تطبيقات جافا
على سبيل المثال ، سأعرض واجهة برمجة تطبيقات RESTful صغيرة مكتوبة بلغة Java / Spring ، مع Gradle كأداة لإدارة البناء والاعتمادية التي ستقوم بتحميل العقد الذكي في blockchain ، وأداء وظيفة تغيير حالة العقد وقراءة الحالة من العقد الذكي.
قبل البدء في تطوير نفسه ، لا بد لي من توضيح شيء ما. على الرغم من حقيقة أن النصاب القانوني لديه خياران للمعاملات رسميًا ، إلا أنني أفضل تقسيمهما إلى 3 أنواع:
- المعاملات العامة - المعاملات مرئية بالكامل لجميع المشاركين في الشبكة (بما في ذلك الحمولة النافعة ) ، ولا تشارك عقدة الجيب في معالجة المعاملة أو تخزينها. لا تختلف المعاملات العامة في النصاب عن المعاملات في شبكة Ethereum ؛
- المعاملات المسموح بها - المعاملات خاصة بشكل أساسي ، ولكن بالنسبة إلى العديد من المشاركين في الشبكة ، أي على الشبكة العامة ، لدينا معلومات حول المعاملة وحالة تنفيذها ، ولكن بدلاً من الحمولة الفعلية على الشبكة العامة ، لدينا فقط سلسلة تجزئة 64 بت ، وهي بواسطة معرف لحمولة حمولة حقيقية في عقدة جيب ، تكون عقدة الجيب نفسها مسؤولة عن توقيع الحمولة النافعة وتشفيرها وتخزينها وتوزيعها بين الأطراف المشار إليها في المعاملة ؛
- المعاملات الخاصة - تختلف عن المسموح بها في أن المعاملة متاحة فقط للعقدة التي أنشأت هذه المعاملة ، ولا يمكن للمشاركين الآخرين في الشبكة رؤية معاملات الحمولة النافعة .
سوف استخدم هذا التصنيف في جميع أنحاء المادة.
للبدء ، سأريكم كيف سيبدو ملف gradle.build
- gradle.build
:
plugins { id 'org.springframework.boot' version '2.1.6.RELEASE' id 'java' } apply plugin: 'io.spring.dependency-management' group = 'com.github' version = '1.0' sourceCompatibility = '1.8' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } test { testLogging.showStandardStreams = true } dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation group: 'org.web3j', name: 'quorum', version: '4.0.6' implementation group: 'org.web3j', name: 'core', version: '4.1.0' implementation group: 'org.web3j', name: 'codegen', version: '4.1.0' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'io.projectreactor:reactor-test' } task generateWrappers(type: JavaExec) { group 'Demo' description 'Generates wrappers for smart-contracts' classpath = sourceSets.main.runtimeClasspath main = 'com.github.quorum.utils.WrappersGenerator' }
قليلا من التفسير:
org.web3j.core
- التبعية للعمل مع المعاملات على شبكة Ethereum والمعاملات العامة على شبكة النصابorg.web3j.quorum
- التبعية للعمل مع المعاملات الخاصة على شبكة النصابorg.web3j.codegen
- التبعية لإنشاء org.web3j.codegen
الذكية- generWrappers - مهمة Gradle لتوليد أغلفة Java من عقود Solidity الذكية
بعد ذلك ، QuorumDemo.sol
لك رمز العقد الذكي الذي سيتم استخدامه في هذه المقالة: ملف QuorumDemo.sol
:
pragma solidity 0.5.0; /** * @dev Smart-Contract for demonstration purposes. */ contract QuorumDemo { string public user; /** * @dev Rewrite user name in storage. */ function writeUser(string calldata _user) public { user = _user; } }
تم جعل العقد بسيطًا عن قصد ، ولكنه يكفي لأغراض مقالتنا. إذا كنت تعرف Solidity ، فيمكنك تخطي التفسير:
string public user
- متغير عام من نوع السلسلة واسم المستخدم . بخلاف Java ، تنشئ Solidity تلقائيًا متغيرات للمتغيرات العامة ، لذلك لا تحتاج إلى تنفيذها يدويًا.function writeUser(...)
- وظيفة تغيير قيمة المتغير ، في الواقع - setter .
لإنشاء برنامج Java-wrapper
من عقد ذكي ، تحتاج إلى وضع الملف في مجلد src/main/solidity/contracts
بأي اسم ، على سبيل المثال QuorumDemo.sol
.
بعد ذلك ، قم بتشغيل Gradle-task createWrappers باستخدام الأمر:
gradle generateWrappers
بعد الانتهاء من هذه المهمة ، سيتم إنشاء برنامج Java-wrapper في src/main/java/com/github/quorum/component/wrappers
، والتي يمكنك التعامل معها بالفعل في كود Java .
لكي تتمكن الواجهة الخلفية من توقيع المعاملات ، نحتاج إلى أن نكون قادرين على تلقي معاملات الحمولة النافعة قبل إرسالها. للقيام بذلك ، سيكون من الجيد الحصول عليها مباشرة من فئة برنامج Java-wrapper . أنا هنا خلقت 2 طرق داخل المجمع. الطريقة الأولى تُرجع ببساطة ABI للعقد ، والتي يمكن استخدامها لتنزيل عقد ذكي جديد. الطريقة الثانية هي تكوين معاملة لتغيير حالة العقد الذكي. هنا هو رمز لهذه الأساليب:
public static String getBinary() { return BINARY; } public static String getDataOnWriteUser(final String user) { final Function function = new Function( FUNC_WRITEUSER, Arrays.asList(new Utf8String(user)), Collections.emptyList() ); return FunctionEncoder.encode(function); }
عن طريق إدخالها في برنامج Java-wrapper الذي تم إنشاؤه ، يمكنك استلام بيانات حمولة للمعاملات.
بعد ذلك ، نحتاج إلى طريقة ملائمة لإرسال المعاملات إلى blockchain ، ويفضل أن يكون ذلك مع نفس الواجهة للمعاملات الخاصة والعامة. لذلك ، قمت بإنشاء واجهة مدير معاملة و 2 من تنفيذها:
TesseraTransactionManager
، لإرسال المعاملات الخاصةGethTransactionManager
، لإرسال المعاملات العامة
لنفصلهم. رمز TesseraTransactionManager
:
@Slf4j public class TesseraTransactionManager implements TransactionManager { private static final byte ATTEMPTS = 20; private static final int SLEEP_DURATION = 100; private final Quorum quorum; private final String fromAddress; private final QuorumTransactionManager quorumTxManager; private final TransactionReceiptProcessor txReceiptProcessor; public TesseraTransactionManager( Quorum quorum, Credentials credentials, String publicKey, List<String> privateFor, Tessera tessera ) { this.quorum = quorum; this.fromAddress = credentials.getAddress(); this.quorumTxManager = new QuorumTransactionManager(quorum, credentials, publicKey, privateFor, tessera); this.txReceiptProcessor = new PollingTransactionReceiptProcessor(quorum, SLEEP_DURATION, ATTEMPTS); } @Override public TransactionReceipt executeTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data) { while (true) { try { final EthSendTransaction ethSendTx = sendTransaction(gasPrice, gasLimit, to, data); if (ethSendTx.hasError() && NONCE_TOO_LOW_ERROR_MESSAGE.equals(ethSendTx.getError().getMessage())) { log.warn("[BLOCKCHAIN] try to re-send transaction cause error {}", ethSendTx.getError().getMessage()); continue; } return processResponse(ethSendTx); } catch (TransactionException ex) { log.error("[BLOCKCHAIN] exception while receiving TransactionReceipt from Quorum node", ex); throw new RuntimeException(ex); } catch (Exception ex) { log.error("[BLOCKCHAIN] exception while sending transaction to Quorum node", ex); throw new RuntimeException(ex); } } } private EthSendTransaction sendTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data) throws IOException { final BigInteger nonce = getNonce(); final RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, data); return this.quorumTxManager.signAndSend(rawTransaction); } private TransactionReceipt processResponse(final EthSendTransaction transactionResponse) throws IOException, TransactionException { if (transactionResponse.hasError()) { throw new RuntimeException( "[BLOCKCHAIN] error processing transaction request: " + transactionResponse.getError().getMessage() ); } final String transactionHash = transactionResponse.getTransactionHash(); return this.txReceiptProcessor.waitForTransactionReceipt(transactionHash); } private BigInteger getNonce() throws IOException { final EthGetTransactionCount ethGetTxCount = this.quorum.ethGetTransactionCount( this.fromAddress, DefaultBlockParameterName.PENDING).send(); return ethGetTxCount.getTransactionCount(); } }
TransactionReceipt executeTransaction(...)
- تنفيذ الواجهة ، وهي طريقة لتنفيذ المعاملات على الشبكة ومعالجة الأخطاء في حالة حدوثها. إرجاع كائن نتيجة معاملة ؛EthSendTransaction sendTransaction(...)
- طريقة لتوقيع وإرسال المعاملات إلى blockchain. إرجاع كائن مع حالة المعاملة وتجزئة لها ؛TransactionReceipt processResponse(...)
- طريقة تنتظر المعاملة لإكمال وإرجاع TransactionReceipt
بعد تنفيذها ؛BigInteger getNonce()
- إرجاع "nonce" من الشبكة.
ورمز GethTransactionManager
:
@Slf4j public class GethTransactionManager extends FastRawTransactionManager implements TransactionManager { private static final byte ATTEMPTS = 20; private static final int SLEEP_DURATION = 100; private final TransactionReceiptProcessor txReceiptProcessor; public GethTransactionManager(Web3j web3j, Credentials credentials) { this(web3j, credentials, new PollingTransactionReceiptProcessor(web3j, SLEEP_DURATION, ATTEMPTS)); } public GethTransactionManager(Web3j web3j, Credentials credentials, TransactionReceiptProcessor txReceiptProcessor) { super(web3j, credentials, txReceiptProcessor); this.txReceiptProcessor = txReceiptProcessor; } @Override public TransactionReceipt executeTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data) { while (true) { try { final EthSendTransaction ethSendTx = sendTransaction(gasPrice, gasLimit, to, data, BigInteger.ZERO); if (ethSendTx != null && ethSendTx.hasError() && NONCE_TOO_LOW_ERROR_MESSAGE.equals(ethSendTx.getError().getMessage())) { log.warn("[BLOCKCHAIN] try to re-send transaction cause error: {}", ethSendTx.getError().getMessage()); continue; } return this.txReceiptProcessor.waitForTransactionReceipt(ethSendTx.getTransactionHash()); } catch (TransactionException ex) { log.error("[BLOCKCHAIN] exception while receiving TransactionReceipt from Quorum node", ex); throw new RuntimeException(ex); } catch (IOException ex) { log.error("[BLOCKCHAIN] exception while sending transaction to Quorum node", ex); throw new RuntimeException(ex); } } } @Override public EthSendTransaction sendTransaction( final BigInteger gasPrice, final BigInteger gasLimit, final String to, final String data, final BigInteger value ) throws IOException { return super.sendTransaction(gasPrice, gasLimit.add(BigInteger.valueOf(21_000L)), to, data, value); } }
TransactionReceipt executeTransaction(...)
- تنفيذ الواجهة ، وهي طريقة لتنفيذ المعاملات على الشبكة ومعالجة الأخطاء في حالة حدوثها. إرجاع كائن نتيجة معاملة ؛EthSendTransaction sendTransaction(...)
هي طريقة تستدعي الأسلوب EthSendTransaction sendTransaction(...)
لإرسال معاملة إلى blockchain.
معالج الطلبات التي تأتي إلى واجهة برمجة التطبيقات :
@Slf4j @Component public class RequestHandler { private final Web3j web3j; private final Quorum quorum; private final Tessera tessera; private final Credentials credentials; private final BlockchainConfig blockchainConfig; private String deployedContract; @Autowired public RequestHandler( @Qualifier("initWeb3j") Web3j web3j, Quorum quorum, Tessera tessera, Credentials credentials, BlockchainConfig blockchainConfig ) { this.web3j = web3j; this.quorum = quorum; this.tessera = tessera; this.credentials = credentials; this.blockchainConfig = blockchainConfig; } /** * Deploy new smart-contract. * * @param serverRequest * - {@link ServerRequest} object with request information * @return {@link ServerResponse} object with response data */ public Mono<ServerResponse> deployContract(final ServerRequest serverRequest) { return serverRequest .bodyToMono(APIRequest.class) .map(this::getTransactionManager) .map(this::deployContract) .flatMap(this::generateResponse); } private TransactionManager getTransactionManager(final APIRequest apiRequest) { log.info("[HANDLER] privateFor = {}", apiRequest.getPrivateFor()); TransactionManager txManager; if (isPrivate(apiRequest.getPrivateFor())) { if (apiRequest.getPrivateFor().size() == 0) { apiRequest.getPrivateFor().add(this.blockchainConfig.getTesseraPublicKey()); } txManager = new TesseraTransactionManager(this.quorum, this.credentials, this.blockchainConfig.getTesseraPublicKey(), apiRequest.getPrivateFor(), this.tessera); } else { txManager = new GethTransactionManager(this.web3j, this.credentials); } return txManager; } private boolean isPrivate(final List<String> limitedTo) { return limitedTo == null || limitedTo.size() == 0 || !limitedTo.get(0).equals("public"); } private APIResponse deployContract(final TransactionManager txManager) { log.info("[HANDLER] deploying new smart-contract"); final String data = QuorumDemo.getBinary(); final TransactionReceipt txReceipt = txManager.executeTransaction(GAS_PRICE, DEPLOY_GAS_LIMIT, null, data); final APIResponse apiResponse = APIResponse.newInstance(txReceipt); this.deployedContract = txReceipt.getContractAddress(); log.info("[HANDLER] contract has been successfully deployed. Result: {}", apiResponse.getMap()); return apiResponse; } private Mono<ServerResponse> generateResponse(final APIResponse apiResponse) { return ServerResponse .ok() .body(Mono.just(apiResponse.getMap()), Map.class); } /** * Send transaction on update user in smart-contract. * * @param serverRequest * - {@link ServerRequest} object with request information * @return {@link ServerResponse} object with response data */ public Mono<ServerResponse> updateUser(final ServerRequest serverRequest) { return serverRequest .bodyToMono(APIRequest.class) .map(this::sendTransaction) .flatMap(this::generateResponse); } private APIResponse sendTransaction(final APIRequest apiRequest) { final TransactionManager txManager = getTransactionManager(apiRequest); log.info("[HANDLER] sending new transaction"); final String data = QuorumDemo.getDataOnWriteUser(apiRequest.getUser()); final TransactionReceipt txReceipt = txManager.executeTransaction(GAS_PRICE, TX_GAS_LIMIT, this.deployedContract, data); final APIResponse apiResponse = APIResponse.newInstance(txReceipt); log.info("[HANDLER] transaction has been successfully executed. Result: {}", apiResponse.getMap()); return apiResponse; } /** * Read user from smart-contract. * * @param serverRequest * - {@link ServerRequest} object with request information * @return {@link ServerResponse} object with response data */ public Mono<ServerResponse> getUser(final ServerRequest serverRequest) { final APIResponse apiResponse = getUser(); return generateResponse(apiResponse); } private APIResponse getUser() { log.info("[HANDLER] reading user from smart-contract"); final QuorumDemo quorumDemo = QuorumDemo.load(this.deployedContract, this.web3j, this.credentials, new StaticGasProvider(GAS_PRICE, DEPLOY_GAS_LIMIT)); final String user = readUserFromSmartContract(quorumDemo); final APIResponse apiResponse = APIResponse.newInstance(user); log.info("[HANDLER] user: '{}'", user); return apiResponse; } private String readUserFromSmartContract(final QuorumDemo quorumDemo) { try { return quorumDemo.user().send().getValue(); } catch (Exception ex) { log.info("[HANDLER] exception while reading user from smart-contract: {}", ex); return null; } } }
الآن سأشرح ما هي الطرق المسؤولة عن ماذا.
طريقة Mono<ServerResponse> deployContract(...)
- تصف المنطق العام Mono<ServerResponse> deployContract(...)
عقد ذكي ، عامًا Mono<ServerResponse> deployContract(...)
.
أسلوب TransactionManager getTransactionManager(...)
- يُرجع كائن تنفيذ مدير المعاملة وفقًا لنوع المعاملة. لهذا ، سوف تحتوي معلمة الطلب على معلمة privateFor ، وهي عبارة عن مجموعة من سلاسل مفاتيح Tessera العامة.
الأسلوب boolean isPrivate(...)
- بإرجاع "صواب" إذا كانت المعلمة privateFor إما فارغة (معاملة خاصة ) أو تحتوي على قائمة بالمفاتيح العامة (معاملة مسموح بها ). إرجاع "خطأ" إذا كانت المعلمة privateFor غير فارغة ، وكان عنصر الصفيف الأول يساوي "public".
APIResponse deployContract(...)
- يرسل المعاملة نشر إلى blockchain.
الأسلوب Mono<ServerResponse> generateResponse(...)
- ينشئ كائنًا مع استجابة للعميل.
أسلوب Mono<ServerResponse> updateUser(...)
- يصف المنطق العام للمعاملة لتغيير حالة العقد الذكي.
APIResponse sendTransaction(...)
- يرسل معاملة تغيير الحالة إلى blockchain.
APIResponse getUser()
- يصف المنطق العام لقراءة المعلومات من عقد ذكي وإرجاع استجابة إلى العميل.
أسلوب String readUserFromSmartContract(...)
- يقرأ الحالة من العقد الذكي ويعيد النتيجة.
يتوفر رمز التطبيق الكامل في مستودع جيثب ، وهو رابط سيكون في نهاية هذه المقالة.
تفتيش
لاختبار جميع أنواع المعاملات الثلاثة ، كتبت فئات اختبار (الرمز موجود في مستودع جيثب ). للقيام بذلك ، قمت بنشر blockchain مع 3 العقد حصة (3 العقد Geth + 3 العقد Tessera ). 3 عقد النصاب هي الحد الأدنى من العقد المطلوبة للتحقق من جميع أنواع المعاملات. ضع ذلك في الاعتبار إذا كنت ترغب في تجربته بنفسك.
المعاملات العامة
لتنفيذ حالة اختبار مع معاملة عامة ، يجب تشغيل الأمر التالي:
gradle test --tests *.PublicTransactionsTests
سترسل حالة الاختبار هذه 3 طلبات لواجهة برمجة التطبيقات . الأول هو نشر العقد الذكي في blockchain ، والثاني هو التغيير في حالة العقد والطلب الثالث هو قراءة المعلومات من العقد الذكي. نتيجة للاختبار ، سترى السجلات التالية تقريبًا (ستختلف العناوين على شبكتك ، وكذلك تجزئة المعاملات):
[HANDLER] privateFor = [public] [HANDLER] deploying new smart-contract [HANDLER] contract has been successfully deployed. Result: {contract_address=0xf9425b94e459805da09950f5988071692d925097, transaction_hash=0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0} [HANDLER] privateFor = [public] [HANDLER] sending new transaction [HANDLER] transaction has been successfully executed. Result: {contract_address=null, transaction_hash=0x33ba66d5deec33f3142bfa190a0d37d0ff07c2e66b06037f5b5ff9578154a3ff} [HANDLER] reading user from smart-contract [HANDLER] user: 'Public Test User'
بشكل عام ، تشير هذه السجلات إلى أن جميع العمليات الثلاث كانت ناجحة. السجلات الثلاثة الأولى - تنتمي إلى طلب نشر العقد الذكي ، السجلات الثلاثة التالية - تنتمي إلى المعاملة ، والأخيران - لقراءة المعلومات من العقد الذكي.
حقيقة أنه في حالة تحميل العقد ، نرى العقد_العنوان ، ولكن في حالة المعاملة البسيطة - لا ، هذا أمر طبيعي تمامًا ، حيث أننا في المرة الثانية لا ننشر العقد ، ولكننا ننفذ المعاملة على عقد ذكي قائم.
الآن دعونا نتحقق مما يعرضه Geth ، قم بتنفيذ الأمر التالي للاتصال بواجهة IPC الخاصة بعملية Geth للعميل :
geth attach /path/to/ipc
بعد أن "اعتدنا" على هذه العملية ، يمكنك مراجعة جميع المعلومات اللازمة بالكامل. دعونا نلقي نظرة على معاملة TransactionReceipt
حول نشر عقد ذكي جديد من خلال تنفيذ الأمر (يجب إعداد التجزئة المعاملة وأخذها من سجلات الاختبار):
web3.eth.getTransactionReceipt('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0');
نتيجة لذلك ، نرى ما يلي:

نحن مهتمون بالمعلمات التالية:
- "contractAddress" - إن لم يكن "خالية" ، فإننا نفهم أن هذه معاملة لنشر عقد ذكي ؛
- "الحالة" - في هذه الحالة ، تساوي "0x1" - مما يعني أن المعاملة كانت ناجحة.
ودعونا ننظر إلى المعاملة نفسها. عن طريق تشغيل الأمر:
web3.eth.getTransaction('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0');
النتيجة:

نحن هنا مهتمون بالمعايير التالية:
- "الإدخال" هو معاملة الحمولة النافعة ؛
- "v" - بشكل عام ، هذه معلمة لـ ECDSA ، خوارزمية التوقيع الرقمي ، لكننا مهتمون الآن بشيء آخر - معنى المتغير. هذا مهم لأنه في المعاملات العامة والخاصة سيكون الأمر مختلفًا. "0x1c" ("28" في النظام العشري) و "0x1b" ("27" في النظام العشري) نموذجي للمعاملات العامة ، و "0x25" ("37" في النظام العشري) و "0x26" ("38" بالتدوين العشري النظام) - هذه هي رموز المعاملات الخاصة.
يمكنك أيضًا التحقق من أن المعلومات على العقد الأخرى لا تختلف عن ما رأيناه الآن.
الآن يمكنك عرض تغييرات حالة المعاملة في العقد الذكي. قم بتشغيل الأمر:
web3.eth.getTransactionReceipt('0x33ba66d5deec33f3142bfa190a0d37d0ff07c2e66b06037f5b5ff9578154a3ff');
النتيجة:

نحن مهتمون بالمعلمات التالية:
- "إلى" - نرى أن الصفقة ذهبت إلى العقد الذكي الذي طال انتظاره ؛
- "الحالة" - تساوي "0x1" ، مما يعني أن المعاملة كانت ناجحة.
الصفقة:

لا شيء غير عادي ، ولكن يمكنك التحقق من المعلومات على العقد الأخرى ، وهذا مفيد.
المعاملات الخاصة
لتنفيذ حالة اختبار مع معاملة خاصة ، يجب تشغيل الأمر التالي:
gradle test --tests *.PrivateTransactionsTests
كما هو الحال في حالة اختبار المعاملات العامة ، ستنشر حالة الاختبار هذه عقدًا ذكيًا جديدًا ، وتنفذ معاملة تغيير الحالة وتقرأ المعلومات من التغيير في العقد الذكي.
نتيجة لذلك ، سيقوم البرنامج بكتابة السجلات التالية:
[HANDLER] privateFor = [] [HANDLER] deploying new smart-contract [HANDLER] contract has been successfully deployed. Result: {contract_address=0x3e2284d92842f781b83cc7e56fbb074ab15f9a90, transaction_hash=0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595} [HANDLER] privateFor = [] [HANDLER] sending new transaction [HANDLER] transaction has been successfully executed. Result: {contract_address=null, transaction_hash=0x72a0458a7b313c8a1c18269ae160e140c6a6e41cb2fd087c64cf665b08a6aefb} [HANDLER] reading user from smart-contract [HANDLER] user: 'Private Test User'
التغيير ، مقارنة بالمعاملات العامة ، هو معلمة privateFor - الآن لديه قيمة صفيف فارغ.
دعنا نتحقق من المعاملات. فريق:
web3.eth.getTransactionReceipt('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595');
النتيجة:

من التغييرات ، بالمقارنة مع المعاملات العامة ، تجدر الإشارة إلى أنك لن ترى كمية الغاز التي تنفق على المعاملة - الغاز المستخدم والمتراكم للغاز المستخدم له قيمة "0".
الآن دعونا نلقي نظرة على المعاملة نفسها. قم بتشغيل الأمر:
web3.eth.getTransaction('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595');
نتيجة لذلك ، سوف نرى هذا:

ما تجدر الإشارة إليه في هذه الصفقة:
- كما ذكرت في بداية هذه المقالة ، فبدلاً من معاملة حمولة حقيقية ، سترى خطًا ثابتًا يتكون من 64 بايت (128 حرفًا) في حقل الإدخال . هذا الخط هو معرف البيانات في مستودع Tessera ، يمكنك الحصول على بيانات حقيقية عند الطلب إلى Tessera .
- "v" - بدلاً من الأكواد "0x1c" أو "0x1b" كما في المعاملات العامة ، سترى "0x26" أو "0x25" للمعاملات الخاصة.
الآن دعونا نتحقق من TransactionReceipt
والمعاملة نفسها لتغيير حالة العقد (أنت تعرف بالفعل الأوامر). النتيجة:


من حيث المبدأ ، لن نتعلم أي شيء جديد من هذه المعاملة الخاصة.
المعاملات المسموح بها
نظرًا لأن هذه المعاملات هي أيضًا معاملات خاصة ، فهي ببساطة خاصة وليس لعقدة واحدة ، ولكن بالنسبة للعديد منها ، لن تختلف نتائج هذه المعاملات عن المعاملات الخاصة. يمكنك إحداث فرق إذا حاولت الحصول على معلومات من عقدة تم تحديدها في privateFor ومن عقدة لم يتم تسجيل مفتاحها العام في privateFor (يمكنك الحصول على معلومات من العقدة الأولى ولا يمكن من الثانية).
لتشغيل حالة اختبار مع المعاملات الخاصة لعدة مشاركين في الشبكة (المعاملات المسموح بها) ، تحتاج إلى تشغيل الأمر التالي:
gradle test --tests *.PermissionTransactionsTests
سجلات واجهة برمجة تطبيقات Java :
[HANDLER] privateFor = [wQTHrl/eqa7TvOz9XJcazsp4ZuncfxHb8c1J1njIOGA=] [HANDLER] deploying new smart-contract [HANDLER] contract has been successfully deployed. Result: {contract_address=0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd, transaction_hash=0x585980bec88aa8a0fe5caffe6d6f24b82d3cd381fcf72fdd8e2102ce67799f01} [HANDLER] privateFor = [wQTHrl/eqa7TvOz9XJcazsp4ZuncfxHb8c1J1njIOGA=] [HANDLER] sending new transaction [HANDLER] transaction has been successfully executed. Result: {contract_address=null, transaction_hash=0x47edc0d00fa9447b2da9f5a78f44602f96145497238cb1ce1d879afb351a3cbe} [HANDLER] reading user from smart-contract [HANDLER] user: 'Permissioned Test User'
ينتج عن عميل Geth ، نشر العقد الذكي الجديد و TransactionReceipt
والمعاملة نفسها ، على التوالي:


وتغيير الحالة المعاملة ، TransactionReceipt
والمعاملة نفسها:


طلبات HTTP
على الرغم من حقيقة أننا رأينا كيف تختلف المعاملات العامة عن المعاملات الخاصة من وجهة نظر عميل Geth ، فإن هذا لا يظهر قيدًا حقيقيًا على الحصول على المعلومات. لذلك ، لكي نوضح لك أنه من الممكن حقًا الحد من عدد العقد التي يمكن أن تقرأ معاملتك ، سأقدم عدة طلبات باستخدام CURL لمدة 3 عقدات لقراءة المعلومات من العقد الذكي (الطلبات ستتعلق بالمعاملات الخاصة والمقبولة ).
سيكون لطلبات HTTP معلمتان في نص الطلب:
- "نقطة النهاية" - نقطة النهاية مباشرة إلى عقدة النصاب ، تحتاج إلى الاتصال بالعقدة.
- "contractAddress" هو عنوان العقد الذي ستُقرأ منه البيانات.
في حالتي ، سيكون لدى "endopint" مضيف واحد - مضيف محلي - ولكن منافذ مختلفة لثلاثة عقد نصاب : 22000 (تم إجراء جميع المعاملات من هذه العقدة) ، 22001 (تم تحديد مفتاحه العام في المعاملات المسموح بها) ، 22002 (يجب ألا يكون لديه حق الوصول إلى المعلومات).
لنبدأ بمعاملة خاصة (يجب أن تكون العقدة على المنفذ 22000 فقط قادرة على عرض المعلومات في عقد ذكي).
طلب CURL على العقدة التي أجرت المعاملة:
curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22000", "contractAddress": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }'
نتيجة لذلك ، حصلنا على ما يلي:
{"data":{"user":"Private Test User"}}
هذا يعني أن العقدة لديها القدرة على عرض المعلومات في عقد ذكي.
الآن دعونا نرى ما العقدة على ميناء 22001 يعود إلينا. طلب حليقة :
curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22001", "contractAddress": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }'
! ممتاز :
{"data":{"status_code":500,"description":"Something went wrong"}}
, - — !
, 3- . CURL :
curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22002", "contractAddress": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }'
! ممتاز API :
{"data":{"status_code":500,"description":"Something went wrong"}}
, . "permissioned" .
CURL "permissioned" - , 22000:
curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22000", "contractAddress": "0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd" }'
:
{"data":{"user":"Permissioned Test User"}}
, , , .
- , -, . CURL :
curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22001", "contractAddress": "0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd" }'
! ممتاز :
{"data":{"user":"Permissioned Test User"}}
, , . CURL :
curl -X POST \ http://127.0.0.1:8080/user \ -H 'Content-Type: application/json' \ -d '{ "endpoint": "http://127.0.0.1:22002", "contractAddress": "0xf1cc0ba22bd0d18fc9acb22dd57795a3f2fb4ebd" }'
! ممتاز -. .
استنتاج
, Quorum blockchain Java . , - .
:
- Quorum
- Quorum
- GitHub
- Quorum Slack
شكرا لاهتمامكم!