Back to Home

Net :: Ethereum Solidity Contract Module

perl · Solidity · Ethereum · geth · DApp · web3.js · Rinkeby · blockchain · cryptocurrency · smart contract · CPAN · Net :: Ethereum

Net :: Ethereum Solidity Contract Module



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

    We plan to use Ethereum Solidity smart contracts in our SAAS online store service written in Perl in the near future. Therefore, I had no choice but to write my Net :: Ethereum module (this module is already available on CPAN, albeit in alpha version).

    I hope that the Net :: Ethereum module will be useful to those who want to integrate their Perl systems with Ethereum blockchain contracts. I will be very grateful to those who use this module and send me their thoughts on its completion, as well as information about the errors found.

    Why I made my module for Perl


    Many believe that Perl has outlived its own, and find many reasons for this. Perhaps if I started creating my service now, I would choose a different 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-established technology, with a lot of documentation and books, including in Russian, available for quick development by beginners. In addition, the CPAN repository, which contains many useful modules, helped focus on solving application problems.

    Now, when the task was to integrate the online store service with Solidity contracts, it turned out that there are tools and frameworks only for JavaScript and Python. In this case, as I understand it, only Web3 library that provides JavaScript-based APIs received official support.

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

    Rewriting our SAAS service in Python or JavaScript is theoretically possible, however, 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 Solidity smart contracts without any intermediate microservices.

    Ethereum JSON RPC API will help us


    The Ethereum network node can serve as a server that provides the JSON RPC programming interface for all the actions we need. A detailed description has been published for this interface . In addition, a description of the management interfaces Management APIs will be helpful .

    Calling most of the functions of the Ethereum JSON RPC API is quite trivial. However, in order to pass parameters to the smart script constructor, its methods, and also to get the values ​​returned by the methods, it is necessary to implement packaging (marshaling, marshaling) and unpacking (unmarshaling). You can’t do without the so-called binary interface specification published in the Application Binary Interface Specification .

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

    Another difficulty in implementing marshaling is that Solidity contracts work with very large numbers - int256, uint256. Perl scripts can work with such numbers using the Math :: BigInt module. This module plugs into Net :: Ethereum.

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

    • uint (uint8 ... uint256)
    • int (int8 ... int256)
    • bool
    • address
    • string

    In the future, I plan to do marshaling for other Solidity data types.

    Preparing the environment for using Net :: Ethereum


    All the work on creating and debugging the Net :: Ethereum module I performed in the cloud, on a Ubuntu 16.04.3 LTS xenial virtual machine. In doing so, I used the Ethereum private network deployed on this virtual machine and consisting of a single node.

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

    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 creating an account, you will be asked for a password that you must save.

    In the next step, initialize the node:

    geth --datadir node1 init genesis.json
    

    After initialization is complete, start the node in the first window of the console 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 specified the parameters --rpc and --rpcapi, they allow the node to provide the services we need. In addition, using the --mine parameter, we launched the 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 commands of the Web3 interface, 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 the launch of Perl scripts working with the Net :: Ethereum module, they need to be run in a separate, third console window. First you need to 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 located in the CPAN directory .

    Create and publish a 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 called HelloSol.sol.

    To compile and deploy the contract, I wrote a small script, 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 coincide with the file name without the extension ".sol"., As well as the password for the account that was saved when preparing the Ethereum node for work.

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

    Then, using the eth_accounts method, the program receives an array of accounts created on the current node, using the first of them to work.

    Compilation and publication of the contract is performed by the compile_and_deploy_contract method. He is given the name and parameters of the contract, the address of the account on behalf of which the contract will be published, as well as the password for this account.

    The compile_and_deploy_contract method compiles the contract source code file by creating the binary interface file specification abi and the binary code file of the contract in the build subdirectory 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.

    Publishing is performed using the deploy_contract method, while waiting for the transaction to complete is performed by the wait_for_contract method. After the publication is completed, we get the contract code using the eth_getCode method to verify that the contract was published successfully.

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

    Thus, you can create your own script for publishing and deploying a contract. It can be integrated into the continuous development and deployment of your system software.

    Work with contract methods


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

    Debug_contract.pl file
    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 compilation and publication script of the contract), the second is the account password, and the third is the address of the contract that was deployed to the console by 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 saves it in the $ src_account variable. On behalf of this account, we will send transactions.

    In order for us to call the methods of the contract, we need to load the contents of the specification file of the binary interface abi, and also save the address of the contract 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 transaction


    If the contract methods do not create transactions (for example, they return a value 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 pVal4 variable of type int256 gets a very large negative value. In the corresponding field, the contract_method_call method will return a value of type Math :: BigInt.

    Transactional Method Call


    If you need to set the value of the data field of the Solidity class, you will have to call the method that initiates the transaction (similar to how 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 transferred values ​​of $ function_params and estimate the amount of gas needed to complete the transaction using the contract_method_call_estimate_gas method. A 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 it to complete. This method returns the status of the transaction, which you can check.

    Conclusion


    Read also my kinini and articles about Ethereum:


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

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

    Read Next