본문 바로가기
프로그래밍/블록체인

[java & ethereum] Smart Contract(Lottery 시스템)를 web3j를 통해 통신해보기 - basic

by 사바라다 2019. 9. 9.
  1. [ethereum]Ethereum 설치 및 실행
  2. [ethereum]Smart Contract(Lottery 시스템) 제작
  3. [ethereum]ethereum과 통신해보기
  4. [ethereum]Smart Contract(Lottery 시스템)과 통신해보기 - basic
  5. [ethereum]Smart Contract(Lottery 시스템)과 통신해보기 - event & log
  6. [ethereum & java]web3j를 통해 ethereum과 통신해보기
  7. [ethereum & java]Smart Contract(Lottery 시스템)과 web3j를 통해 통신해보기 - basic
  8. Smart Contract(Lottery 시스템)과 web3j를 통해 통신해보기 - event & log

안녕하세요. 저번시간에 우리는 web3j 오픈소스를 이용해서 ethereum과 통신해보았습니다. 저번시간에 해봤던 테스트들은 세팅확인용, 또는 계정이 가지고 있는 Balance확인, 계정 정보확인 등 공통으로 사용하는 method들이었습니다. 하지만 우리의 직접 제작한 Smart Contract와 적절한 통신을 할 수 있어야 실무에서 그리고 목표의 완성이라고 할 수 있겠습니다.

 

오늘은 web3j를 이용하여 Smart Contract와 직접통신할 수 있는 방법에 대해서 알아보도록 하겠습니다.

Web3j Transaction 소개

web3j transaction 구조 : https://docs.web3j.io/transactions/

 

위 그림은 web3j 공식문서에서 설명하는 trancation에 대한 흐름도입니다. 위의 Ethereum Transaction이 잘 이해가 안되시는 분은 제 포스팅중에 [Ethereum]Ethereum의 Transaction 처리 Life Cycle 분석이라는 포스팅을 한적이 있습니다. 해당 포스팅을 참고하시면 도움이 되실거라고생각됩나다.

 

web3j는 위와 같은 flow에 따라서 내부 로직을 잘 모르더라도 쉽게 ethereum과 통신할 수 있도록 잘 wrapping되어 있는 오픈소스입니다. 이전 4번째 시간에 post 통신을 통해서 기본적인 transaction을 구현해봤었습니다. web3j는 이런 ethereum의 json-rpc 규약의 잘 지킨 오픈소스이므로 통신하는 원리는 같습니다.

 

이렇게 web3j의 transaction의 소개를마치고 그럼 직접 구현해보도록 하겠습니다.

Smart Contract

smartContract는 4번째 시간의 코드와 동일합니다.

pragma solidity >=0.4.21 <0.6.0;

contract Lottery
{
    address payable public owner;
    uint 256 private _pot;

    contructor() public
    {
        owner = msg.sender;
    }

    function getPot() public view returns(uint256)
    {
        return _pot;
    }

    function getNumber(uint256 num) public pure returns(uint256)
    {
        return num;
    }

    function getOwner() public view returns(address)
    {
        return owner;
    }

    function setPot(uint256 pot) public
    {
        _pot = pot;
    }
}

이렇게 코드를 작성한 후 compile & migrate를 진행해줍니다. 저는 ganache-cli를 ehtereum network로 사용하며, truffle을 이용해 컴파일과 배포를 진행했습니다.

 

사용할 1번계정은 0xF76c9B7012c0A3870801eaAddB93B6352c8893DB, private key는 0x8d22a0aa9c43da157ebc24bc7d70c26d198381e042ab93434757752e3f0ee8e5, contract 주소는 0x55815f746a53574523296831B33c2277B2760562 이렇게 구성되었습니다. 이제 이 값을 가지고 smartContract를 호출해보도록 하겠습니다.

 

  • 일반적이라면 private key는 공개하면 안됩니다만, ganache-cli는 항상 어느 pc에서 접속하든 동일한 계정과 private key를 반환하기 때문에 공개합니다.

Java Code_get

이번 보여드리는 예제는 sync를 사용하도록 하겠습니다. async와 Rx는 web3j를 통해 ethereum과 통신해보기을 참고하시어 진행해보시면 좋을것 같습니다.

private String from = public String getPot() throws IOException, ExecutionException, InterruptedException {

        // 1. 호출하고자 하는 function 세팅[functionName, parameters]
        Function function = new Function("getPot",
                                         Collections.emptyList(),
                                         Arrays.asList(new TypeReference<Uint256>() {}));

        // 2. ethereum을 function 변수로 통해 호출
        return ethereumService.ethCall(function);
    }
public String ethCall(Function function) throws IOException {

            //3. transaction 제작
            Transaction transaction = Transaction.createEthCallTransaction(from, contract,
                                                                           FunctionEncoder.encode(function));

            //4. ethereum 호출후 결과 가져오기
            EthCall ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();

            //5. 결과값 decode
            List<Type> decode = FunctionReturnDecoder.decode(ethCall.getResult(),
                                                             function.getOutputParameters());

            System.out.println("ethCall.getResult() = " + ethCall.getResult());
            System.out.println("getValue = " + decode.get(0).getValue());
            System.out.println("getType = " + decode.get(0).getTypeAsString());

            return (String)decode.get(0).getValue();
    }

get에 대해서 구현해보았습니다. ethereum에서의 get function은 block에 data를 insert하거나 update하지 않습니다. 그렇기 때문에 별도의 Transaction비용이 발생하지 않습니다. 그리고 결과를 바로 알기 위해서 eth_call을 구현한 ethCall method를 통해서 구현하였습니다. flow를 설명드리겠습니다.

 

  1. smartContract안에서 호출할 method의 이름과 parameter를 입력하여 ABI module을 제작한다. 내부 로직에 의해서 어떻게 변화되는지는 Smart Contract(Lottery 시스템)과 통신해보기 - basic 포스팅을 확인해주시면 됩니다.
  2. ethCall이라는 제작한 method를 호출한다.
  3. from Address, contract Address, function을 ABI로 encoding한 데이터를 통해 transaction가능한 데이터가 되도록 제작합니다. (이전 포스팅의 json-rpc 형식의 데이터)
  4. ethereum을 sync로 호출한 후 response를 가져온다.
  5. ABI encoing되어있는 결과값을 decoding한다.

결과값

ethCall.getResult() = 0x0000000000000000000000000000000000000000000000000000000000000000
getValue = 0
getType = uint256

Java Code_set

public void setPot(int num) throws IOException, ExecutionException, InterruptedException {
        // 1. 호출하고자 하는 function 세팅[functionName, parameters]
        Function function = new Function("setPot",
                                         Arrays.asList(new Uint256(num)),
                                         Collections.emptyList());

        // 2. sendTransaction
        String txHash = ethereumService.ethSendTransaction(function);

        // 7. getReceipt
        TransactionReceipt receipt = ethereumService.getReceipt(txHash);
        System.out.println("receipt = " + receipt);
    }
public String ethSendTransaction(Function function)
            throws IOException, InterruptedException, ExecutionException {

        // 3. Account Lock 해제
        PersonalUnlockAccount personalUnlockAccount = web3j.personalUnlockAccount(from, pwd).send();

        if (personalUnlockAccount.accountUnlocked()) { // unlock 일때

            //4. account에 대한 nonce값 가져오기.
            EthGetTransactionCount ethGetTransactionCount = web3j.ethGetTransactionCount(
                    from, DefaultBlockParameterName.LATEST).sendAsync().get();

            BigInteger nonce = ethGetTransactionCount.getTransactionCount();

            //5. Transaction값 제작
            Transaction transaction = Transaction.createFunctionCallTransaction(from, nonce,
                                                                                Transaction.DEFAULT_GAS,
                                                                                null, contract,
                                                                                FunctionEncoder.encode(function));

            // 6. ethereum Call
            EthSendTransaction ethSendTransaction = web3j.ethSendTransaction(transaction).send();

            // transaction에 대한 transaction Hash값 얻기.
            String transactionHash = ethSendTransaction.getTransactionHash();

            // ledger에 쓰여지기 까지 기다리기.
            Thread.sleep(5000);

            return transactionHash;
        }
        else {
            throw new PersonalLockException("check ethereum personal Lock");
        }
    }
public TransactionReceipt getReceipt(String transactionHash) throws IOException {

        //8. transaction Hash를 통한 receipt 가져오기.
        EthGetTransactionReceipt transactionReceipt = web3j.ethGetTransactionReceipt(transactionHash).send();

        if(transactionReceipt.getTransactionReceipt().isPresent())
        {
            // 9. 결과확인
            System.out.println("transactionReceipt.getResult().getContractAddress() = " +
                               transactionReceipt.getResult());
        }
        else
        {
            System.out.println("transaction complete not yet");
        }

        return transactionReceipt.getResult();
    }

set은 ethereum의 block에 data를 쓰는 행위가 있습니다. 이런 행위 자체는 비용이 발생합니다. transaction 비용이 발생하는 건에 대해서는 eth_call이 아닌 eth_sendTransaction을 사용해야할 필요가있습니다. 또한 중복 요청등을 막기위해서 nonce를 이용합니다.

 

  1. setPot이라는 function을 호출하기 위한 이에 맞는 ABI encoding어야 합니다. Function은 그 기본이 됩니다.
  2. 직접 만든 함수를 호출합니다.
  3. 한 계정에서 비용이 발생하는 transaction을 발생시키기 위해서는 계정을 해제(본인인증)을 할 필요가 있습니다. 따라서 PersonUnlockAccount를 통하여 unlock을 진행할 필요가 있습니다.
  4. account에 대한 nonce값을 가져옵니다.
  5. ethereum에 전송할 데이터를 제작합니다.
  6. ethereum을 호출하고 transaction Hash값을 얻어옵니다. eth_sendTransaction은 바로 결과값을 가져올 수 없습니다. 결과값은 block에 쓰여졌을 때 log의 형태로 가져올 수 있습니다.
  7. ethSendTransaction method를 통해 호출 해서 반환받은 transaction Hash값을 통하여 Receipt를 얻기위한 method입니다.
  8. eth_GetTransactionReceipt를 호출합니다.
  9. transactionHash를 확인합니다. (결과값을 보시면 data가 없는것을 알 수 있습니다. 데이터를 receipt에서 바로 확인하시고 싶으시면, logs가 출력이되어야 합니다. 이것은 event가 호출되었을 경우에만 호출되어집니다. 그렇지 않다면 return값을 가져야 하구요. event는 다음시간에 보겠습니다. )

결과값

transactionReceipt.getResult().getContractAddress() = TransactionReceipt{transactionHash='0xca7c02dfd1c62833f8d77a792508ac71675e8ad1661cf7323500feb721a2f961', transactionIndex='0x0', blockHash='0xa3bcab3137ae976376b52f3304c3f90b9ed5cda5869f5e662f962dc9577cde48', blockNumber='0x3', cumulativeGasUsed='0xa2f5', gasUsed='0xa2f5', contractAddress='null', root='null', status='0x1', from='0xf76c9b7012c0a3870801eaaddb93b6352c8893db', to='0x55815f746a53574523296831b33c2277b2760562', logs=[], logsBloom='0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'}

마무리

오늘은 web3j를 이용하여 ethereum의 smart-contract와 통신을 주고받는 것을 보여드렸습니다.

 

github에 소스를 올려두었으니 소스를 참조하시기 바랍니다.

 

solidity 소스

https://github.com/KoangHoYeom/Ethereum-ClientTest/blob/master/web3jClient/solidity/lotteryEx/contracts/Lottery.sol

 

KoangHoYeom/Ethereum-ClientTest

Ethereum JSON-RPC for study ! Contribute to KoangHoYeom/Ethereum-ClientTest development by creating an account on GitHub.

github.com

 

java 소스

https://github.com/KoangHoYeom/Ethereum-ClientTest/blob/master/web3jClient/src/main/java/personal/blockchain/domain/basic/BasicService.java

 

KoangHoYeom/Ethereum-ClientTest

Ethereum JSON-RPC for study ! Contribute to KoangHoYeom/Ethereum-ClientTest development by creating an account on GitHub.

github.com

 

감사합니다!

참조

https://docs.web3j.io/transactions/

 

Transactions - Web3j

 Transactions Broadly speaking there are three types transactions supported on Ethereum: Transfer of Ether from one party to another Creation of a smart contract Transacting with a smart contract To undertake any of these transactions, it is necessary to

docs.web3j.io

https://github.com/ethereum/wiki/wiki/JSON-RPC

 

ethereum/wiki

The Ethereum Wiki. Contribute to ethereum/wiki development by creating an account on GitHub.

github.com

 

댓글