Halo, Habr!
Bagi mereka yang tertarik dengan masalah blockchain, bukan rahasia lagi bahwa selain blockchain publik, seperti Ethereum , Bitcoin , Zcash , dll., Ada juga "perusahaan (pribadi)" "saudara" mereka yang dalam beberapa hal lebih baik daripada jaringan publik, tetapi dalam sesuatu mereka kalah dari mereka. Di antara jaringan yang paling terkenal, saya pikir Anda dapat memberi nama Quorum (vendor - JP Morgan Chase ), Pantheon (vendor - PegaSys ) dan Hyperledger (dikelola oleh The Linux Foundation ). Terlepas dari kenyataan bahwa ada cukup banyak keputusan publik, bisnis semakin tertarik pada blockchain pribadi karena mereka mampu memberikan tingkat privasi yang diperlukan, transaksi lebih cepat dan sebagainya. Perbedaan antara blockchain swasta dan publik, serta kelebihan dan kekurangannya, bukan topik artikel ini. Jika Anda tertarik untuk membacanya, artinya, misalnya, artikel tentang Media .
Dalam artikel ini, saya ingin memberi tahu Anda bagaimana Anda dapat menggunakan blockchain Quorum untuk mengembangkan aplikasi Anda dengan dukungan untuk transaksi pribadi dan publik. Untuk menunjukkan kemampuannya, kami akan menulis aplikasi kecil Java / Spring yang akan menerima permintaan untuk menyebarkan (menyebarkan) kontrak pintar, melakukan transaksi dan membaca data dari kontrak pintar. Sebenarnya, inilah tumpukan teknologi yang akan digunakan dalam artikel:
Quorum adalah proyek dengan kode sumber terbuka di GitHub , yang tujuannya adalah untuk menyediakan blockchain yang akan memungkinkan untuk melakukan transaksi tidak hanya secara publik tetapi juga dalam mode pribadi juga. Dari sudut pandang teknis, Quorum adalah peningkatan Ethereum , ia juga memiliki klien Geth yang dimodifikasi untuk dapat melakukan transaksi pribadi.
Tambahan penting juga layanan kantong , yang bertanggung jawab untuk penyimpanan, enkripsi dan distribusi transaksi pribadi di antara mereka sendiri. Sekarang ada 2 layanan enklave tersebut :
- Konstelasi - ditulis dalam Haskell, versi kantong pertama , tetapi sekarang tidak berkembang lagi, dan kemungkinan besar di masa depan itu akan ditinggalkan demi yang baru;
- Tessera - layanan baru, ditulis di Jawa , didukung oleh pengembang dari JP Morgan Chase, memiliki lebih banyak opsi untuk integrasi dengan database dan pengelolaan informasi sensitif (misalnya, ada opsi integrasi dengan HashiCorp Vault untuk mengelola rahasia).
Adapun transaksi, dari sudut pandang antarmuka Ethereum biasa , tidak banyak yang berubah (dan ini bagus). Untuk mengirim transaksi pribadi, selain informasi biasa tentang transaksi, Anda juga harus menentukan parameter privateFor - ini adalah larik baris, dan baris ini adalah kunci publik dari simpul kantong . Dengan menggunakan kunci ini, transaksi payload dienkripsi dan payload didistribusikan antara node Tessera di dalam blockchain.
Untuk perkenalan yang lebih mendalam dengan Quorum , cara kerjanya, dan cara meningkatkan jaringan blockchain, Anda dapat menemukannya di situs web resmi (tautan ke dokumentasi, serta tautan ke tutorial tentang cara meluncurkan blockchain uji, saya akan tinggalkan di akhir artikel).
Pengembangan Aplikasi Java
Sebagai contoh, saya akan menunjukkan API tenang kecil yang ditulis di Java / Spring , dengan Gradle sebagai alat manajemen membangun dan ketergantungan yang akan memuat kontrak pintar ke dalam blockchain, melakukan fungsi mengubah keadaan kontrak dan membaca keadaan dari kontrak pintar.
Sebelum memulai pengembangan itu sendiri, saya harus mengklarifikasi sesuatu. Terlepas dari kenyataan bahwa Quorum secara resmi memiliki 2 opsi transaksi, saya lebih suka membaginya menjadi 3 jenis:
- Transaksi publik - transaksi sepenuhnya dapat dilihat oleh semua peserta jaringan (termasuk muatan ), simpul kantong tidak mengambil bagian dalam memproses atau menyimpan transaksi. Transaksi publik dalam Kuorum tidak berbeda dengan transaksi dalam jaringan Ethereum ;
- Transaksi yang diizinkan - transaksi pada dasarnya bersifat pribadi, tetapi untuk beberapa peserta jaringan, yaitu di jaringan publik, kami memiliki informasi tentang transaksi dan status pelaksanaannya, tetapi alih-alih payload nyata di jaringan publik, kami hanya memiliki string hash 64-bit, yang merupakan oleh pengidentifikasi untuk muatan nyata dalam simpul kantong , simpul kantong itu sendiri bertanggung jawab untuk menandatangani, mengenkripsi, menyimpan dan mendistribusikan muatan antara pihak-pihak yang ditunjukkan untuk transaksi;
- Transaksi pribadi - berbeda dari yang diizinkan karena transaksi hanya tersedia untuk simpul yang membuat transaksi ini, peserta jaringan lain tidak dapat melihat transaksi payload .
Saya akan menggunakan klasifikasi ini di seluruh artikel.
Untuk memulai, saya akan menunjukkan kepada Anda seperti apa file build itu - 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' }
Sedikit penjelasan:
org.web3j.core
- ketergantungan untuk bekerja dengan transaksi di jaringan Ethereum dan transaksi publik di jaringan Kuorumorg.web3j.quorum
- ketergantungan untuk bekerja dengan transaksi pribadi di jaringan Kuorumorg.web3j.codegen
- ketergantungan untuk menghasilkan org.web3j.codegen
untuk kontrak pintar Solidity- generateWrappers - Gradle-task untuk menghasilkan pembungkus Java dari kontrak pintar Solidity
Selanjutnya, saya akan menunjukkan kepada Anda kode kontrak pintar yang akan digunakan dalam artikel ini: File 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; } }
Kontrak sengaja dibuat sederhana, tetapi cukup untuk keperluan artikel kami. Jika Anda tahu Solidity , maka Anda dapat melewatkan penjelasan:
string public user
- variabel publik tipe string dan nama pengguna . Tidak seperti Java , Solidity secara otomatis menghasilkan pengambil untuk variabel publik, jadi Anda tidak perlu mengimplementasikannya secara manual.function writeUser(...)
- fungsi mengubah nilai variabel, pada kenyataannya - setter .
Untuk membuat Java-wrapper
dari kontrak pintar, Anda harus meletakkan file di folder src/main/solidity/contracts
dengan nama apa pun, misalnya QuorumDemo.sol
.
Selanjutnya, jalankan Gradle-task generateWrappers dengan perintah:
gradle generateWrappers
Setelah menyelesaikan tugas ini, Java-wrapper akan dibuat di src/main/java/com/github/quorum/component/wrappers
, yang sudah dapat Anda gunakan dengan kode Java .
Agar backend dapat menandatangani transaksi, kami harus dapat menerima transaksi payload sebelum kami mengirimkannya. Untuk melakukan ini, akan menyenangkan untuk mendapatkannya langsung dari kelas Java-wrapper . Di sini saya membuat 2 metode di dalam bungkusnya. Metode pertama hanya mengembalikan ABI kontrak, yang dapat digunakan untuk mengunduh kontrak pintar baru. Metode kedua adalah pembentukan transaksi untuk mengubah status kontrak pintar. Berikut ini kode untuk metode ini:
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); }
Dengan memasukkannya ke dalam pembungkus Java yang dihasilkan, Anda dapat menerima payload untuk transaksi.
Selanjutnya, kita memerlukan cara yang mudah untuk mengirim transaksi ke blockchain, lebih disukai dengan antarmuka yang sama untuk transaksi pribadi dan publik. Oleh karena itu, saya membuat antarmuka manajer transaksi dan 2 implementasinya:
TesseraTransactionManager
, untuk mengirim transaksi pribadiGethTransactionManager
, untuk mengirim transaksi publik
Mari kita pisahkan mereka. Kode 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(...)
- implementasi antarmuka, metode untuk melakukan transaksi di jaringan dan menangani kesalahan jika terjadi. Mengembalikan objek dengan hasil transaksi;EthSendTransaction sendTransaction(...)
- metode untuk menandatangani dan mengirim transaksi ke blockchain. Mengembalikan objek dengan status transaksi dan hash-nya;TransactionReceipt processResponse(...)
- metode yang menunggu transaksi untuk menyelesaikan dan mengembalikan TransactionReceipt
setelah pelaksanaannya;BigInteger getNonce()
- Mengembalikan "nonce" dari jaringan.
Dan kode 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(...)
- implementasi antarmuka, metode untuk melakukan transaksi di jaringan dan menangani kesalahan jika terjadi. Mengembalikan objek dengan hasil transaksi;EthSendTransaction sendTransaction(...)
adalah metode yang memanggil metode EthSendTransaction sendTransaction(...)
untuk mengirim transaksi ke blockchain.
Handler untuk permintaan yang datang ke API :
@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; } } }
Sekarang saya akan menjelaskan metode apa yang bertanggung jawab untuk apa.
Metode Mono<ServerResponse> deployContract(...)
- menjelaskan logika umum Mono<ServerResponse> deployContract(...)
kontrak pintar, baik negeri maupun swasta.
Metode TransactionManager getTransactionManager(...)
- mengembalikan objek implementasi manajer transaksi tergantung pada jenis transaksi. Untuk ini, parameter permintaan akan berisi parameter privateFor , yang merupakan larik string kunci publik Tessera .
Metode boolean isPrivate(...)
- mengembalikan "true" jika parameter privateFor kosong (transaksi pribadi ) atau memiliki daftar kunci publik (transaksi yang diijinkan ). Mengembalikan "false" jika parameter privateFor tidak kosong, dan elemen array pertama sama dengan "publik".
APIResponse deployContract(...)
- mengirimkan transaksi deploy ke blockchain.
Metode Mono<ServerResponse> generateResponse(...)
- menghasilkan objek dengan respons ke klien.
Metode Mono<ServerResponse> updateUser(...)
- menjelaskan logika umum transaksi untuk mengubah status kontrak pintar.
APIResponse sendTransaction(...)
- mengirimkan transaksi perubahan status ke blockchain.
APIResponse getUser()
- menjelaskan logika umum untuk membaca informasi dari kontrak pintar dan mengembalikan respons kepada klien.
Metode String readUserFromSmartContract(...)
- membaca status dari kontrak pintar dan mengembalikan hasilnya.
Kode aplikasi lengkap tersedia di repositori Github , tautan yang akan ada di akhir artikel ini.
Periksa
Untuk menguji semua 3 jenis transaksi, saya menulis kelas uji (kode ada di repositori GitHub ). Untuk melakukan ini, saya menggunakan blockchain dengan 3 kuorum node (3 geth node + 3 node Tessera ). 3 node kuorum adalah node minimum yang diperlukan untuk memverifikasi semua jenis transaksi. Ingatlah hal ini jika Anda ingin mencobanya sendiri.
Transaksi publik
Untuk menjalankan uji kasus dengan transaksi publik, Anda harus menjalankan perintah berikut:
gradle test --tests *.PublicTransactionsTests
Kasing uji ini akan mengirim 3 permintaan API . Yang pertama adalah penyebaran kontrak pintar ke blockchain, yang kedua adalah perubahan status kontrak dan permintaan ketiga adalah pembacaan informasi dari kontrak pintar. Sebagai hasil dari pengujian, Anda akan melihat kira-kira log berikut (alamat pada jaringan Anda akan berbeda, dan juga hash transaksi):
[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'
Secara umum, log ini menunjukkan bahwa semua 3 operasi berhasil. 3 log pertama - milik permintaan untuk penyebaran kontrak pintar, 3 log berikutnya - milik transaksi, dan 2 log terakhir - untuk membaca informasi dari kontrak pintar.
Fakta bahwa dalam hasil pemuatan kontrak kita melihat contract_address , tetapi dalam kasus transaksi sederhana - tidak, ini cukup normal, karena kedua kalinya kita tidak menggunakan kontrak, tetapi melakukan transaksi pada kontrak pintar yang ada.
Sekarang mari kita periksa apa yang ditunjukkan oleh Geth kepada kita , jalankan perintah berikut untuk terhubung ke antarmuka IPC proses Geth klien:
geth attach /path/to/ipc
Setelah kami βterbiasa denganβ prosesnya, Anda dapat meninjau sepenuhnya semua informasi yang diperlukan. Mari kita lihat transaksi TransactionReceipt
pada penerapan kontrak pintar baru dengan mengeksekusi perintah (hash transaksi harus disiapkan dan diambil dari log pengujian):
web3.eth.getTransactionReceipt('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0');
Hasilnya, kita melihat yang berikut:

Kami tertarik pada parameter berikut:
- "contractAddress" - jika bukan "null", maka kami memahami bahwa ini adalah transaksi untuk penyebaran kontrak cerdas;
- "status" - dalam hal ini, sama dengan "0x1" - yang berarti transaksi berhasil.
Dan mari kita lihat transaksi itu sendiri. Dengan menjalankan perintah:
web3.eth.getTransaction('0x31bc179f8cd12c640d1663f3df51ce6da1fbc2875f2b724c3911108fcd19a5d0');
Hasil:

Di sini kita tertarik pada parameter berikut:
- "input" adalah transaksi payload ;
- "v" - secara umum, ini adalah parameter untuk ECDSA , algoritma tanda tangan digital, tetapi sekarang kami tertarik pada hal lain - arti dari variabel. Ini penting karena dalam transaksi publik dan pribadi akan berbeda. "0x1c" ("28" dalam sistem desimal) dan "0x1b" ("27" dalam sistem desimal) adalah tipikal untuk transaksi publik, dan "0x25" ("37" dalam sistem desimal) dan "0x26" ("38" dalam desimal sistem) - ini adalah kode transaksi pribadi.
Anda juga dapat memeriksa bahwa pada node lain informasinya tidak berbeda dari yang kita lihat sekarang.
Sekarang Anda dapat melihat perubahan status transaksi dari kontrak pintar. Jalankan perintah:
web3.eth.getTransactionReceipt('0x33ba66d5deec33f3142bfa190a0d37d0ff07c2e66b06037f5b5ff9578154a3ff');
Hasil:

Kami tertarik pada parameter berikut:
- "ke" - kita melihat bahwa transaksi beralih ke kontrak pintar yang ditunggu-tunggu saja;
- "status" - sama dengan "0x1", yang berarti transaksi berhasil.
Transaksi:

Tidak ada yang aneh, tetapi Anda dapat memeriksa informasi pada node lain, ini berguna.
Transaksi Pribadi
Untuk menjalankan uji kasus dengan transaksi pribadi, Anda harus menjalankan perintah berikut:
gradle test --tests *.PrivateTransactionsTests
Seperti dalam uji kasus dengan transaksi publik, uji kasus ini akan menggunakan kontrak pintar baru, melaksanakan transaksi perubahan negara dan membaca informasi dari perubahan dalam kontrak pintar.
Akibatnya, program akan menulis log berikut:
[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'
Perubahan, dibandingkan dengan transaksi publik, adalah parameter privateFor - sekarang memiliki nilai array kosong.
Mari kita periksa TransactionReceipt
untuk transaksi. Tim:
web3.eth.getTransactionReceipt('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595');
Hasil:

Dari perubahan tersebut, dibandingkan dengan transaksi publik, perlu dikatakan bahwa Anda tidak akan melihat jumlah gas yang dihabiskan untuk transaksi - gasUsed dan kumulatifGasUsed memiliki nilai "0".
Sekarang mari kita lihat transaksi itu sendiri. Jalankan perintah:
web3.eth.getTransaction('0x8fd619bd9a526f83e29d7b417551e174862f7503ef430eb45793509d05039595');
Sebagai hasilnya, kita akan melihat ini:

Apa yang perlu diperhatikan dalam transaksi ini:
- Seperti yang saya sebutkan di awal artikel ini, alih-alih transaksi payload nyata, Anda akan melihat garis tetap 64 byte (128 karakter) di bidang input . Baris ini adalah pengidentifikasi untuk data dalam repositori Tessera , Anda bisa mendapatkan data nyata berdasarkan permintaan ke Tessera .
- "v" - alih-alih kode "0x1c" atau "0x1b" seperti dalam transaksi publik, untuk transaksi pribadi Anda akan melihat "0x26" atau "0x25".
Sekarang mari kita periksa TransactionReceipt
dan transaksi itu sendiri untuk mengubah status kontrak (Anda sudah tahu perintahnya). Hasil:


Pada prinsipnya, kami tidak akan mempelajari hal baru dari transaksi pribadi ini.
Transaksi yang Diijinkan
Karena ini juga merupakan transaksi pribadi, mereka hanya bersifat pribadi, bukan untuk 1 simpul, tetapi untuk beberapa node, hasil dari transaksi tersebut tidak akan berbeda dari transaksi pribadi. Anda dapat membuat perbedaan jika Anda mencoba untuk mendapatkan informasi dari node yang ditentukan dalam privateFor dan dari node yang kunci publiknya tidak terdaftar di privateFor (Anda bisa mendapatkan informasi dari node pertama dan tidak bisa dari yang kedua).
Untuk menjalankan kasus uji dengan transaksi pribadi untuk beberapa peserta jaringan (transaksi yang diizinkan), Anda perlu menjalankan perintah berikut:
gradle test --tests *.PermissionTransactionsTests
Log API 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'
Hasil dalam klien Geth , pada penyebaran kontrak pintar baru, TransactionReceipt
dan transaksi itu sendiri, masing-masing:


Dan status mengubah transaksi, TransactionReceipt
dan transaksi itu sendiri:


Permintaan HTTP
Terlepas dari kenyataan bahwa kami melihat bagaimana transaksi publik berbeda dari yang pribadi dari sudut pandang klien Geth , ini tidak menunjukkan batasan nyata dalam memperoleh informasi. Oleh karena itu, untuk menunjukkan kepada Anda bahwa sangat mungkin untuk membatasi jumlah node yang dapat membaca transaksi Anda, saya akan membuat beberapa permintaan menggunakan CURL selama 3 node untuk membaca informasi dari kontrak pintar (permintaan akan menyangkut transaksi pribadi dan pengiriman ).
Permintaan HTTP akan memiliki 2 parameter di badan permintaan:
- "endpoint" - secara langsung endpoint ke simpul Kuorum , Anda harus terhubung ke simpul tersebut.
- "contractAddress" adalah alamat kontrak dari mana data akan dibaca.
Dalam kasus saya, "endopint" akan memiliki satu host - localhost - tetapi port yang berbeda untuk 3 node Kuorum : 22000 (semua transaksi dilakukan dari node ini), 22001 (kunci publiknya ditentukan dalam transaksi yang diizinkan), 22002 (seharusnya tidak memiliki akses ke informasi).
Mari kita mulai dengan transaksi pribadi (hanya node pada port 22000 yang dapat melihat informasi dalam kontrak pintar).
Permintaan CURL pada simpul yang melakukan transaksi:
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" }'
Hasilnya, kami mendapat yang berikut:
{"data":{"user":"Private Test User"}}
Ini berarti bahwa node memiliki kemampuan untuk melihat informasi dalam kontrak pintar.
Sekarang mari kita lihat apa node pada port 22001 kembali kepada kita. Permintaan 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": "0x3e2284d92842f781b83cc7e56fbb074ab15f9a90" }'
Hebat! :
{"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" }'
Hebat! 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" }'
Hebat! :
{"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" }'
Hebat! -. .
Kesimpulan
, Quorum blockchain Java . , - .
:
- Quorum
- Quorum
- GitHub
- Quorum Slack
Terima kasih atas perhatian anda!