📜 ⬆️ ⬇️

Net :: Ethereum module for working with Solidity contracts



Usually, when I need to use some kind of new service or technology from Perl scripts, I go to CPAN, and there is already one or more suitable modules. However, in the case of frameworks for working with Ethereum blockchain nodes and Solidity contracts, unfortunately, I could not find the required module.

We plan to soon use the smart contracts Solidity of the Ethereum network in our SAAS-service of online stores, written in Perl. So I had no choice but to write my own Net :: Ethereum module (this module is already available on CPAN, albeit in the form of an alpha version).
')
I hope that the Net :: Ethereum module will be useful to those who want to integrate their Perl systems with the Ethereum blockchain contracts. I would be very grateful to those who use this module and send me their thoughts on how to improve it, as well as information about errors found.

Why I made my Perl module


Many believe that Perl has become obsolete, and find many reasons for this. Perhaps, if I started creating my service now, I would choose another programming language, such as Python or Golang. However, the choice was made more than 10 years ago, and then using Perl was the right decision. It was a reliable, well-developed technology, with a lot of documentation and books, including in Russian, available for quick mastering by beginners. In addition, the CPAN repository, which contains many useful modules, helped to focus on solving applied problems.

Now, when the task of integrating the online store service with Solidity contracts arose, it turned out that there are tools and frameworks only for JavaScript and Python. At the same time, as far as I understand, only Web3 library, providing the API based on JavaScript, received official support.

Of course, we could raise the Node.js node and create a microservice on it for the interaction of Perl scripts and Solidity smart contracts. However, the cost of creating and maintaining this node, ensuring its high load capacity and fault tolerance would be added.

It is theoretically possible to rewrite our SAAS service in Python or JavaScript, but it will require incredible financial costs and a lot of time. As a result, I decided that it would be easier to write the Net :: Ethereum module and integrate with the Solidity smart contracts without any intermediate microservices.

Ethereum JSON RPC API will help us.


The node (node) of the Ethereum network can serve as a server providing the JSON RPC programming interface for performing all the necessary actions. For this interface published a detailed description . In addition, a description of the Management APIs will be helpful.

Calling most of the Ethereum JSON RPC API functions is fairly trivial. However, in order to pass the parameters to the designer of the smart script, its methods, and also to get the values ​​returned by the methods, it is necessary to implement packing (marshaling, marshaling) and unpacking (unmarshaling). It does not do without the so-called specification of the binary interface, published in the document Application Binary Interface Specification .

Examining this specification may require some effort. If you decide to understand all the details of the marshaling, then you, like me, will be helped by the article Working with smart contracts through the Ethereum RPC API .

Another difficulty with marshaling is related to the fact that Solidity contracts work with very large numbers - int256, uint256. Perl scripts can work with such numbers using the Math :: BigInt module. This module connects to Net :: Ethereum.

I must say that at the moment (version 0.28) marshaling (and unmarshaling) are implemented only for the following data types:


In the future, I plan to marshal for other types of Solidity data.

Preparing the environment for using Net :: Ethereum


I did all the work on creating and debugging the Net :: Ethereum module in the cloud, on the Ubuntu virtual machine on 04/16/3 LTS xenial. At the same time, I used a private Ethereum network deployed on this virtual machine and consisting of one node.

First of all, in the home directory of a user with normal permissions, you need to create a file genesis.json:

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


Next, create an account:

 geth --datadir node1 account new 

When you create an account, you will be asked for a password that you want to save.

In the next step, we initialize the node:

 geth --datadir node1 init genesis.json 

After the initialization is complete, launch the node in the first console window with the following command:

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

Please note that we have specified the --rpc and --rpcapi parameters, they allow the node to provide the services we need. In addition, using the - mine parameter, we launched local mining, which is necessary for publishing contracts and performing other transactions.

After starting the node, we do not enter any commands in the first console window, but simply observe the messages that appear there.

To work with Web3 interface commands, open the second console window, and run the command to connect to the node there:

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

As for running Perl scripts that work with the Net :: Ethereum module, they need to be run in a separate, third console window. You must first install the Net :: Ethereum module, and also make sure that the latest version of the Math :: BigInt module is installed.

The Net :: Ethereum module is in the CPAN directory .

Create and publish test smart contract


The Net :: Ethereum module was tested on the following contract:

HelloSol.sol file
 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); } } 


Save this contract in the working directory, a file named HelloSol.sol.

To compile and deploy a contract, I wrote a small deploy_contract.pl, presented below.

File 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"; 


This script needs to be passed the name of the Solidity class, which must match the file name without the ".sol" extension, as well as the account password saved when preparing the Ethereum node for operation.

The contract compilation and publishing program connects to the site at localhost : 8545 /. If this address is not available, check the node start command.

Next, the program using the eth_accounts method gets an array of accounts created on the current node, using the first one to work.

We compile and publish the contract by compile_and_deploy_contract. He is given the name and parameters of the contract, the address of the account on whose behalf the contract will be published, as well as the password of this account.

The compile_and_deploy_contract method compiles the contract source code file by creating the abi binary interface specification file and the binary code code file in the build directory of the working directory. To do this, use the following command:

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

Next, the compile_and_deploy_contract method unlocks the account using the personal_unlockAccount method, estimates the amount of gas required for publication using the deploy_contract_estimate_gas method.

Publication is performed by the method deploy_contract, while waiting for the completion of the transaction executes the method wait_for_contract. After the publication is completed, we receive the contract code using the eth_getCode method to make sure that the contract was published successfully.

After the publication is completed, the compile_and_deploy_contract method returns the status of the contract. Our publishing program retrieves and displays the address of a published contract, the transaction hash, and the amount of gas used. The cost of publishing a contract is displayed in units of wei and ether.

This way you can create your own script for publishing and deploying a contract. It can be integrated into a continuous development and deployment system for your system software.

Work with contract methods


To work with a contract, we used the debug_contract.pl script shown below.

File 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); 


As the first parameter, the script needs to pass the name of the contract class (as for the script for compiling and publishing the contract), the second is the account password, and the third is the address of the contract, which was printed to the console by the program deploy_contract.pl.

At the very beginning of its work, the debug_contract.pl script receives the address of the first account created on the node and stores it in the $ src_account variable. On behalf of this account we will send transactions.

In order for us to call the contract methods, we need to load the contents of the binary interface abi specification file, and also save the contract address in the object:

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


Calling methods without creating a transaction


If contract methods do not create transactions (for example, return values ​​from variables, constants, or literals), then we can use the contract_method_call method:

 $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); 

Note that the variable pVal4 of type int256 gets a very large negative value. In the appropriate field, the method contract_method_call will return a value of type Math :: BigInt.

Calling Transactional Methods


If you need to set the value of the data field of the Solidity class, you will have to call the method that triggers the transaction (just as we did when publishing the contract).

To do this, we first need to unlock the account using the personal_unlockAccount method.

Next, we prepare a hash with the passed $ function_params values ​​and estimate the amount of gas needed to complete the transaction using the contract_method_call_estimate_gas method. The transaction is sent using the sendTransaction method:

 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); 

Next, using the wait_for_transaction method, we wait for its completion. This method returns the status of the transaction, which you can check.

Conclusion


Read also my articles:


I would like to express special gratitude to the author of the article description of management interfaces Management APIs . This article helped me to deal with the most confusing in Ethereum JSON RPC API - packaging and unpacking of parameters for the designer of a smart contract and its methods.

I also hope that the Net :: Ethereum module will help projects created in the Perl programming language to integrate with the Ethereum blockchain.

Source: https://habr.com/ru/post/346234/


All Articles