ネット:: Ethereum Solidity Contractモジュール





通常、Perlスクリプトから新しいサービスまたはテクノロジーを使用する必要がある場合、CPANにアクセスしますが、すでに1つ以上の適切なモジュールがあります。 ただし、EthereumブロックチェーンノードとSolidityコントラクトを使用するためのフレームワークの場合、残念ながら適切なモジュールが見つかりませんでした。



近い将来、Perlで書かれたSAASオンラインストアサービスでEthereum Solidityスマートコントラクトを使用する予定です。 したがって、Net :: Ethereumモジュールを作成する以外に選択肢はありませんでした(このモジュールは、アルファ版ではありますが、CPANで既に利用可能です)。



Net :: Ethereumモジュールが、PerlシステムをEthereumブロックチェーンコントラクトと統合したい人に役立つことを願っています。 このモジュールを使用してくれた方々に感謝し、完成したことについての考えや発見されたエラーに関する情報を送ってくれます。



Perl用にモジュールを作成した理由



多くの人は、Perlが独自に生き延びたと信じており、これには多くの理由があります。 おそらく今すぐサービスを作成し始めたら、PythonやGolangなどの別のプログラミング言語を選択するでしょう。 ただし、選択は10年以上前に行われたため、Perlを使用することが正しい判断でした。 信頼性が高く、定評のある技術であり、ロシア語を含む多くのドキュメントや書籍があり、初心者でも簡単に開発できます。 さらに、多くの便利なモジュールを含むCPANリポジトリは、アプリケーションの問題の解決に集中するのに役立ちました。



現在、オンラインストアサービスをSolidityコントラクトと統合することがタスクの場合、JavaScriptとPython専用のツールとフレームワークがあることが判明しました。 この場合、私が理解したように、JavaScriptベースのAPIを提供するWeb3ライブラリのみが公式サポートを受けました。



もちろん、Node.jsノードを上げて、PerlスクリプトとSolidityスマートコントラクトの相互作用のためのマイクロサービスを作成することもできます。 ただし、このノードを作成して維持し、その高い負荷容量とフォールトトレランスを確保するためのコストが追加されます。



PythonまたはJavaScriptでSAASサービスを書き換えることは理論的には可能ですが、信じられないほどの金銭的費用と多くの時間が必要になります。 その結果、Net :: Ethereumモジュールを記述し、中間のマイクロサービスなしでSolidityスマートコントラクトと統合する方が簡単だと判断しました。



Ethereum JSON RPC APIが役立ちます



Ethereumネットワークノードは、必要なすべてのアクションにJSON RPCプログラミングインターフェイスを提供するサーバーとして機能できます。 このインターフェイスの詳細な説明が公開されています。 さらに、管理インターフェイスManagement APIの説明も役立ちます。



Ethereum JSON RPC APIのほとんどの関数を呼び出すことは非常に簡単です。 ただし、スマートスクリプトコンストラクターとそのメソッドにパラメーターを渡し、メソッドから返される値を取得するには、パッケージ化(マーシャリング、マーシャリング)およびアンパック(アンマーシャリング)を実装する必要があります。 Application Binary Interface Specificationで公開されている、いわゆるバイナリインターフェイス仕様なしではできません。



この仕様を検討するには、ある程度の努力が必要な場合があります。 マーシャリングのすべての詳細を理解することにした場合、あなたは、私のように、Ethereum RPC APIを介したスマートコントラクトの操作の記事から利益を得ます



マーシャリングの実装におけるもう1つの困難は、Solidityコントラクトが非常に大きな数(int256、uint256)で機能することです。 Perlスクリプトは、Math :: BigIntモジュールを使用して、そのような数値を処理できます。 このモジュールは、Net :: Ethereumにプラグインします。



現時点(バージョン0.28)でのマーシャリング(およびアンマーシャリング)は、次のデータ型に対してのみ実装されていると言わなければなりません。





将来、他のSolidityデータタイプのマーシャリングを行う予定です。



Net :: Ethereumを使用するための環境の準備



クラウドで、Ubuntu 16.04.3 LTS xenial仮想マシンで実行したNet :: Ethereumモジュールの作成とデバッグに関するすべての作業。 その際、この仮想マシンにデプロイされた単一ノードで構成されるイーサリアムプライベートネットワークを使用しました。



まず、通常の権限を持つユーザーのホームディレクトリで、genesis.jsonファイルを作成する必要があります。



Genesis.jsonファイル
{ "config": { "chainId": 1907, "homesteadBlock": 0, "eip155Block": 0, "eip158Block": 0 }, "difficulty": "40", "gasLimit": "5100000", "alloc": {} }
      
      







次に、アカウントを作成します。



 geth --datadir node1 account new
      
      





アカウントを作成するとき、保存する必要があるパスワードを求められます。



次の手順で、ノードを初期化します。



 geth --datadir node1 init genesis.json
      
      





初期化が完了したら、次のコマンドを使用して、コンソールの最初のウィンドウでノードを起動します。



 geth --datadir node1 --nodiscover --mine --minerthreads 1 --maxpeers 0 --verbosity 3 --networkid 98765 --rpc --rpcapi="db,eth,net,web3,personal,web3" console
      
      





パラメーター--rpcおよび--rpcapiを指定したことに注意してください。これらのパラメーターにより、ノードは必要なサービスを提供できます。 さらに、-mineパラメーターを使用して、ローカルマイニングを開始しました。これは、契約の公開およびその他のトランザクションの実行に必要です。



ノードを起動した後、最初のコンソールウィンドウにはコマンドを入力せずに、そこに表示されるメッセージを確認します。



Web3インターフェースのコマンドを使用するには、2番目のコンソールウィンドウを開き、コマンドを実行してそこでノードに接続します。



 geth --datadir node1 --networkid 98765 attach ipc://home/frolov/node1/geth.ipc
      
      





Net :: Ethereumモジュールで動作するPerlスクリプトの起動に関しては、別の3番目のコンソールウィンドウで実行する必要があります。 最初に、Net :: Ethereumモジュールをインストールする必要があります。また、最新バージョンのMath :: BigIntモジュールがインストールされていることを確認してください。



Net :: EthereumモジュールはCPANディレクトリにあります。



テストスマートコントラクトを作成して公開する



Net :: Ethereumモジュールは、次の契約でテストされました。



HelloSol.solファイル
 pragma solidity ^0.4.10; contract HelloSol { string savedString; uint savedValue; address contractOwner; function HelloSol(uint initValue, string initString) public { contractOwner = msg.sender; savedString = initString; savedValue = initValue; } function setString( string newString ) public { savedString = newString; } function getString() public constant returns( string curString) { return savedString; } function setValue( uint newValue ) public { savedValue = newValue; } function getValue() public constant returns( uint curValue) { return savedValue; } function setAll(uint newValue, string newString) public { savedValue = newValue; savedString = newString; } function getAll() public constant returns( uint curValue, string curString) { return (savedValue, savedString); } function getAllEx() public constant returns( bool isOk, address msgSender, uint curValue, string curString, uint val1, string str1, uint val2, uint val3) { string memory sss="++ ==================================== ++"; return (true, msg.sender, 33333, sss, 9999, "Line 9999", 7777, 8888); } function repiter(bool pBool, address pAddress, uint pVal1, string pStr1, uint pVal2, string pStr2, uint pVal3, int pVal4) public pure returns( bool rbBool, address rpAddress, uint rpVal1, string rpStr1, uint rpVal2, string rpStr2, uint rpVal3, int rpVal4) { return (pBool, pAddress, pVal1, pStr1, pVal2, pStr2, pVal3, pVal4); } }
      
      







このコントラクトを作業ディレクトリ(HelloSol.solというファイル)に保存します。



契約をコンパイルしてデプロイするために、以下に示す小さなスクリプトdeploy_contract.plを作成しました。



ファイルdeploy_contract.pl
 #!/usr/bin/perl use strict; use Net::Ethereum; use Data::Dumper; my $contract_name = $ARGV[0]; my $password = $ARGV[1]; my $node = Net::Ethereum->new('http://localhost:8545/'); my $src_account = $node->eth_accounts()->[0]; print 'My account: '.$src_account, "\n"; my $constructor_params={}; $constructor_params->{ initString } = '+ Init string for constructor +'; $constructor_params->{ initValue } = 102; my $contract_status = $node->compile_and_deploy_contract($contract_name, $constructor_params, $src_account, $password); my $new_contract_id = $contract_status->{contractAddress}; my $transactionHash = $contract_status->{transactionHash}; my $gas_used = hex($contract_status->{gasUsed}); print "\n", 'Contract mined.', "\n", 'Address: '.$new_contract_id, "\n", 'Transaction Hash: '.$transactionHash, "\n"; my $gas_price=$node->eth_gasPrice(); my $contract_deploy_price = $gas_used * $gas_price; my $price_in_eth = $node->wei2ether($contract_deploy_price); print 'Gas used: '.$gas_used.' ('.sprintf('0x%x', $gas_used).') wei, '.$price_in_eth.' ether', "\n\n";
      
      







このスクリプトには、Solidityクラスの名前を渡す必要があります。これは、拡張子「.sol」のないファイル名と一致する必要があります。また、Ethereumノードを作業用に準備するときに保存したアカウントのパスワード。



コントラクトのコンパイルおよび公開プログラムは、 localhost :8545 /のホストに接続します。 このアドレスが使用できない場合は、node startコマンドを確認してください。



次に、eth_accountsメソッドを使用して、プログラムは現在のノードで作成されたアカウントの配列を受け取り、最初のアカウントを使用して動作します。



契約のコンパイルと公開は、compile_and_deploy_contractメソッドによって実行されます。 彼には、契約の名前とパラメーター、契約を発行するアカウントのアドレス、およびこのアカウントのパスワードが与えられます。



compile_and_deploy_contractメソッドは、バイナリインターフェイスインターフェイス仕様ファイルabiと、作業ディレクトリのビルドサブディレクトリにコントラクトのバイナリコードファイルを作成することにより、コントラクトソースコードファイルをコンパイルします。 これを行うには、次のコマンドを使用します。



 my $cmd = "$bin_solc --bin --abi $contract_src_path -o build --overwrite";
      
      





次に、compile_and_deploy_contractメソッドはpersonal_unlockAccountメソッドを使用してアカウントのロックを解除し、deploy_contract_estimate_gasメソッドを使用して公開に必要なガスの量を推定します。



公開はdeploy_contractメソッドを使用して実行され、wait_for_contractメソッドはトランザクションが完了するまで待機します。 発行が完了したら、eth_getCodeメソッドを使用して契約コードを取得し、契約が正常に発行されたことを確認します。



公開が完了すると、compile_and_deploy_contractメソッドは契約のステータスを返します。 パブリッシングプログラムは、パブリッシュされた契約のアドレス、トランザクションハッシュ、および使用されたガスの量を取得して表示します。 契約の公開費用は、weiおよびetherの単位で表示されます。



このようにして、契約用の独自のパブリケーションおよび展開スクリプトを作成できます。 システムの継続的な開発およびソフトウェア展開システムに統合できます。



契約方法を使用する



コントラクトを操作するために、以下に示すdebug_contract.plスクリプトを使用しました。



Debug_contract.plファイル
 use Net::Ethereum; use Data::Dumper; my $contract_name = $ARGV[0]; my $password = $ARGV[1]; my $contract_id = $ARGV[2]; my $node = Net::Ethereum->new('http://localhost:8545/'); my $src_account = $node->eth_accounts()->[0]; print 'My account: '.$src_account, "\n"; my $abi = $node->_read_file('build/'.$contract_name.'.abi'); $node->set_contract_abi($abi); $node->set_contract_id($contract_id); # Call contract methods without transactions my $function_params={}; my $test1 = $node->contract_method_call('getValue', $function_params); print Dumper($test1); my $test = $node->contract_method_call('getString'); print Dumper($test); my $testAll = $node->contract_method_call('getAll'); print Dumper($testAll); my $testAllEx = $node->contract_method_call('getAllEx'); print Dumper($testAllEx); $function_params={}; $function_params->{ pBool } = 1; $function_params->{ pAddress } = "0xa3a514070f3768e657e2e574910d8b58708cdb82"; $function_params->{ pVal1 } = 1111; $function_params->{ pStr1 } = "This is string 1"; $function_params->{ pVal2 } = 222; $function_params->{ pStr2 } = "And this is String 2, very long string +++++++++++++++++========="; $function_params->{ pVal3 } = 333; $function_params->{ pVal4 } = '-999999999999999999999999999999999999999999999999999999999999999977777777'; my $rc = $node->contract_method_call('repiter', $function_params); print Dumper($rc); # Send Transaction 1 my $rc = $node->personal_unlockAccount($src_account, $password, 600); print 'Unlock account '.$src_account.'. Result: '.$rc, "\n"; my $function_params={}; $function_params->{ newString } = '+++ New string for save +++'; my $used_gas = $node->contract_method_call_estimate_gas('setString', $function_params); my $gas_price=$node->eth_gasPrice(); my $transaction_price = $used_gas * $gas_price; my $call_price_in_eth = $node->wei2ether($transaction_price); print 'Estimate Transaction Gas: '.$used_gas.' ('.sprintf('0x%x', $used_gas).') wei, '.$call_price_in_eth.' ether', "\n"; my $tr = $node->sendTransaction($src_account, $node->get_contract_id(), 'setString', $function_params, $used_gas); print 'Waiting for transaction: ', "\n"; my $tr_status = $node->wait_for_transaction($tr, 25, $node->get_show_progress()); print Dumper($tr_status); # Send Transaction 2 $rc = $node->personal_unlockAccount($src_account, $password, 600); print 'Unlock account '.$src_account.'. Result: '.$rc, "\n"; $function_params={}; $function_params->{ newValue } = 77777; $used_gas = $node->contract_method_call_estimate_gas('setValue', $function_params); $transaction_price = $used_gas * $gas_price; $call_price_in_eth = $node->wei2ether($transaction_price); print 'Estimate Transaction Gas: '.$used_gas.' ('.sprintf('0x%x', $used_gas).') wei, '.$call_price_in_eth.' ether', "\n"; $tr = $node->sendTransaction($src_account, $node->get_contract_id(), 'setValue', $function_params, $used_gas); print 'Waiting for transaction: ', "\n"; $tr_status = $node->wait_for_transaction($tr, 25, $node->get_show_progress()); print Dumper($tr_status); $testAllEx = $node->contract_method_call('getAllEx'); print Dumper($testAllEx);
      
      







最初のパラメーターとして、スクリプトはコントラクトのクラス名を渡す必要があります(コントラクトのコンパイルおよび公開スクリプトに関して)、2番目はアカウントパスワード、3番目はdeploy_contract.plによってコンソールにデプロイされたコントラクトのアドレスです。



作業の最初に、debug_contract.plスクリプトはノードで作成された最初のアカウントのアドレスを受け取り、$ src_account変数に保存します。 このアカウントに代わって、トランザクションを送信します。



コントラクトのメソッドを呼び出すことができるように、バイナリインターフェイスabiの仕様ファイルの内容をロードし、さらにコントラクトのアドレスをオブジェクトに保存する必要があります。



 my $abi = $node->_read_file('build/'.$contract_name.'.abi'); $node->set_contract_abi($abi); $node->set_contract_id($contract_id);
      
      







トランザクションなしでメソッドを呼び出す



コントラクトメソッドがトランザクションを作成しない場合(たとえば、変数、定数、またはリテラルから値を返す)、contract_method_callメソッドを使用できます。



 $function_params={}; $function_params->{ pBool } = 1; $function_params->{ pAddress } = "0xa3a514070f3768e657e2e574910d8b58708cdb82"; $function_params->{ pVal1 } = 1111; $function_params->{ pStr1 } = "This is string 1"; $function_params->{ pVal2 } = 222; $function_params->{ pStr2 } = "And this is String 2, very long string +++++++++++++++++========="; $function_params->{ pVal3 } = 333; $function_params->{ pVal4 } = '-999999999999999999999999999999999999999999999999999999999999999977777777'; my $rc = $node->contract_method_call('repiter', $function_params); print Dumper($rc);
      
      





int256型のpVal4変数は非常に大きな負の値を取得することに注意してください。 対応するフィールドで、contract_method_callメソッドは、Math :: BigInt型の値を返します。



トランザクションメソッド呼び出し



Solidityクラスのデータフィールドの値を設定する必要がある場合は、トランザクションを開始するメソッドを呼び出す必要があります(コントラクトの公開時と同様)。



これを行うには、まずpersonal_unlockAccountメソッドを使用してアカウントのロックを解除する必要があります。



次に、転送された$ function_paramsの値でハッシュを準備し、contract_method_call_estimate_gasメソッドを使用してトランザクションを完了するために必要なガスの量を推定します。 トランザクションは、sendTransactionメソッドを使用して送信されます。



 my $function_params={}; $function_params->{ newString } = '+++ New string for save +++'; my $used_gas = $node->contract_method_call_estimate_gas('setString', $function_params); my $gas_price=$node->eth_gasPrice(); my $transaction_price = $used_gas * $gas_price; my $call_price_in_eth = $node->wei2ether($transaction_price); print 'Estimate Transaction Gas: '.$used_gas.' ('.sprintf('0x%x', $used_gas).') wei, '.$call_price_in_eth.' ether', "\n"; my $tr = $node->sendTransaction($src_account, $node->get_contract_id(), 'setString', $function_params, $used_gas); print 'Waiting for transaction: ', "\n"; my $tr_status = $node->wait_for_transaction($tr, 25, $node->get_show_progress()); print Dumper($tr_status);
      
      





次に、wait_for_transactionメソッドを使用して、完了するまで待機します。 このメソッドは、確認できるトランザクションのステータスを返します。



おわりに



私のkininiとイーサリアムに関する記事も読んでください:





管理インターフェース管理APIの説明について記事の著者に特別な感謝を申し上げたいと思います 。 この記事は、最も複雑なEthereum JSON RPC API-スマートコントラクトコンストラクターとそのメソッドのパラメーターのパックとアンパックに対処するのに役立ちました。



また、Net :: Ethereumモジュールが、Perlプログラミング言語で作成されたプロジェクトをEthereumブロックチェーンと統合するのに役立つことを願っています。



All Articles