4. Solidity(Web3.py 단순 저장고)

정예찬·2022년 7월 22일
2

solidity

목록 보기
6/13
post-custom-banner

본 글은 freeCodeCamp.Org의 Youtube 영상 'Solidity, Blockchain, and Smart Contract Course – Beginner to Expert Python Tutorial'와 관련 코드인 SmartContract의 Github 코드를 기초로 작성되었다.

Youtube 영상 링크: https://www.youtube.com/watch?v=M576WGiDBdQ&t=10336s
Github 코드 링크: https://github.com/smartcontractkit/full-blockchain-solidity-course-py
오늘의 코드: https://github.com/PatrickAlphaC/web3_py_simple_storage

이번 포스팅은 유튜브 영상 03:26:48~04:27:55에 해당하는 내용이다.

지난 시간까지는 Remix를 이용해서 코딩을 했다. 이번 시간부터는 Visual Studio Code를 활용해서 코딩을 진행한다. Remix가 Solidity만 실행하기에는 정말 편리하다. 그러나 코드 수준이 높아질수록 Solidity만으로 코딩을 진행하기는 어려워진다. 고차원의 코딩을 위해서 Javascript나 Python과 같은 보편적인 언어와 Solidity를 같이 사용한다. 오늘 이후 포스팅에서는 Python과 Solidity를 같이 활용한다. Remix는 Python을 지원하지 않아 Visual Studio Code라는 새로운 프로그램 설치를 해야 한다.
https://code.visualstudio.com/
위 링크에서 쉽게 설치할 수 있다. 사지방 컴퓨터에는 이러한 유형의 프로그램 설치가 막혀있다. 어쩔 수 없이 goormide에 들어가 학생판 유료 버전을 1달 구매하였다.(goormide 유료 버전에서 visual studio code를 사용할 수 있다.) 1달 후부터는 사회에 있을 예정이어서 1달만 구매했다!!!

좌측에 정사각형 4개 있는 모양의 버튼을 누르면 Extensions가 뜨는데 Python과 Solidity를 각각 검색하여 설치하면 된다. Extensions는 코드 편집에 활용되는 여러 도구들이라고 보면 된다. 다음으로 전세계에서 가장 많이 이용되는 프로그래밍 언어인 Python을 설치해보자.(앞서 설치한 Python Extension과 구분된다.) 방금 설명한 일련의 과정은 Python을 활용하여 Solidity 코딩을 하기 위해 필수적인 과정이다. Python은 아래 링크에서 다운로드할 수 있다.
https://www.python.org/downloads/

컴퓨터와의 상호작용을 하기 위해 terminal(터미널)이라는 입출력 환경을 사용한다.

mkdir demos

'mkdir 디렉토리명'을 입력하면 디렉토리명에 해당하는 디렉토리가 생성된다. 위 코드에서는 demos라는 폴더가 생성되었다.

~# cd demos
~/demos# cd
~# cd demos
~/demos# cd ..
clear

폴더를 넘나들기 위해 'cd'라는 명령어를 사용한다. 'cd 현재위치 기준 폴더위치 및 폴더명'을 입력하면 해당 폴더로 위치한다. 'cd'를 입력하면 최상위 폴더로 이동한다. 'cd ..'를 입력하면 현재위치 기준 한 단계 상위 폴더로 이동한다. 'clear'를 입력하면 작성했던 터미널 기록이 지워진다.

~/demos# mkdir web3_py_simple_storage
~/demos# cd web3_py_simple_storage
~/demos/web3_py_simple_storage#

web3.py를 이용하여 우리가 '1. Solidity' 때 만들었던 단순 저장고(simple storage)를 만들 계획이다. web3.py는 ethereum과 상호작용하는 Python library이다. 즉, Python을 이용해 단순 저장고를 구현해보겠다. 위 코드는 이를 위해 demos 폴더 안에 web3_py_simple_storage라는 폴더를 만들고 web3_py_simple_storage에 위치하는 코드이다.

web3_py_simple_storage 안에 'SimpleStorage.sol'이란 파일을 하나 만들고 우리가 단순 저장고를 만들 때 사용했던 코드를 그대로 붙여넣자. 그리고 'Ctrl+s'를 눌러 코드를 저장하자.
코드 링크: https://github.com/PatrickAlphaC/simple_storage/blob/main/SimpleStorage.sol

영상 중간중간 코드 편집의 편의성을 제고하기 위한 코드 설정이 나오는데 이는 부차적인 부분이니 영상을 보고 따라하길 바란다.

Visual Studio Code에서 Solidity 파일 실행을 위해서는 어떻게 해야 할까? 본 포스팅에서는 python을 활용하여 이를 진행하겠다. web3_py_simple_storage에 'deploy.py'라는 파일을 추가해주자.

with open("./SimpleStorage.sol", "r") as file:
    simple_storage_file = file.read()

Python 문법이 등장했다. 'with open("파일위치 및 파일명", "데이터 처리 방식") as 변수명:' 형식의 의미는 다음과 같다: "특정 데이터 처리 방식으로 파일명에 해당하는 파일을 열어 변수명에 저장하라. 그 후 다음 줄에 나오는 내용을 실행한 후 파일을 닫아라." read() 함수는 '변수명.' 뒤에 붙어 해당 변수의 내용을 읽어들인다. 코드 분석을 해보자. './'는 "현재 폴더 안의"라는 뜻이다. 현재 폴더 안의 'SimpleStorage.sol'라는 파일을 읽기 방식('r'은 읽기, 'w'는 쓰기이다.)으로 열어 file이라는 변수로 저장하자. 그 후 file의 내용을 읽어 simple_storage_file에 저장하라!

print(simple_storage_file)

괄호 안의 내용을 출력하는 print 함수를 이용해 출력을 진행하면 'SimpleStorage.sol'의 내용이 출력됨을 확인할 수 있다.(출력을 해보고 코드를 지우자. 이후 과정에 방해된다.)
Solidity와 Python 코드 작성 방식에서 차이가 하나 발견된다. 바로 문장 끝 세미콜론(;)의 존재 유무다. Solidity의 경우 세미콜론이 붙는 반면 Python은 붙지 않는다.

deploy를 위해선 먼저 컴파일을 해야 한다. 컴파일 진행을 위해 'py-solc-x'라는 툴을 하나 깔아야 한다. 'pip install py-solc-x'라는 명령어를 터미널에 입력해주면 툴이 설치된다.

from solcx import compile_standard, install_solc
import json

여기서부터 영상에는 오류가 있다. 필자도 유튜브대로 코드 실행이 안 돼서 좀 헤맸다. 오류와 수정 사항은 아래 사이트에서 확인 가능하다.
https://github.com/smartcontractkit/full-blockchain-solidity-course-py/blob/main/chronological-issues-from-video.md
설치가 완료되면 deploy.py에 위 코드를 추가해주자. 'import compile_standard'는 다운받은 solcx 툴에서 compile_standard를 불러오는 코드이다. compile_standard는 solcx 컴파일 양식을 정하게 해주는 함수이다. 언어는 무엇을 쓸 것인지, 소스 파일은 무엇인지, 설정은 무엇인지, 버전은 무엇인지... 등등을 정하는 함수이다. 컴파일 과정에서 solc를 설치해야 하는데, 이를 위한 함수 install_solc 또한 불러왔다.
json 형식은 사용되는 언어에 관계 없이 컴파일이 가능한 형식이다. 따라서 Solidity와 Python을 매개하기 위해 불러왔다.

install_solc("0.6.0")
compiled_sol = compile_standard(
	{
    	"language": "Solidity",
    	"sources": {"SimpleStorage.sol": {"content": simple_storage_file}},
        "settings":{
        	"outputSelection": {
            	"*": {
                	"*": ["abi", "metadata", "evm.bytecode", "evm.sourceMap"]}
			}
        }, 
    }
)

solc 버전 0.6.0을 설치한 후 'compile_standard'를 편집하여 'compiled_sol'에 저장하고 있는데, 지나치게 깊은 내용이므로 본 포스팅에서 다루지는 않겠다.

with open("compiled_code.json", "w") as file:
	json.dump(compiled_sol, file)

json 형식으로 컴파일된 코드를 쓰기 형식으로 열어 file에 저장한다. 'json.dump(compiled_sol, file)'은 file에 compiled_sol을 json 형식으로 dump하라는 의미이다. 직관적으로 이야기하자면 solidity 형식으로 컴파일된 파일을 json 형식으로 변환하는 코드이다.

python deploy.py

터미널에 다음 코드를 입력하여 deploy.py를 실행하면 SimpleStorage.sol이 json 형식으로 컴파일된 compiled_code.json이 폴더에 추가된다.

bytecode = compiled_sol["contracts"]["SimpleStorage.sol"]["SimpleStorage"]["evm"][
    "bytecode"
]["object"]

abi = json.loads(
    compiled_sol["contracts"]["SimpleStorage.sol"]["SimpleStorage"]["metadata"]
)["output"]["abi"]

바이트코드(bytecode)와 ABI(abi)를 저장하는 코드이다. 바이트코드를 이해하기 위해서는 먼저 EVM(Ethereum Virtual Machine)을 알아야 한다. EVM은 이더리움 전체를 동작하는 가상의 컴퓨터이다. EVM은 이더리움 노드들에 저장되어 있다.
EVM과 같은 가상 컴퓨터에서 사용하는 코드를 바이트 코드라고 한다.
ABI란 기계어 수준에서 두 모듈이 상호작용할 수 있는 방법이다. 모듈은 간단하게 프로그램 정도로 이해하면 된다.

다음으로 ganache를 설치하자. ganache는 이더리움 네트워크의 여러 기능을 체험해볼 수 있는 단일 노드이다. 아래 사이트에서 쉽게 설치할 수 있는데 사지방에는 설치가 안 된다. 따라서 github코드 기반으로 포스팅을 한 후 전역하고 업데이트하겠다.
https://trufflesuite.com/ganache/

pip install web3

위 코드를 터미널에 입력하여 web3.py를 실행할 준비를 하자. web3.py는 이더리움과 상호작용하는 Python library이다.

from web3 import Web3
import os

파이썬 코드 맨 위에 위 코드를 추가하여 Web3 함수와 os함수를 사용할 수 있게 하자.

w3 = Web3(Web3.HTTPProvider("http://0.0.0.0:8545"))
chain_id = 1337
my_address = "0xdbB4A708755dfD59f9c4b100B2BE23a6d2EB7D57"
private_key = os.getenv("PRIVATE_KEY")

이제 Python 스크립트를 ganache 계좌와 연결해보자.
Web3(Web3.HTTPProvider("RPC 주소"))는 RPC 주소와 연결하는 함수이다. 'http://0.0.0.0:8545'는 ganache 주소이다. 함수의 반환값을 w3에 저장하고 있다.
ganache 체인의 id, 즉 고유번호(위 코드에서는 1337)를 chain_id에 저장하고 있다.
my_address에 ganache 계좌의 주소를 저장하고 있다.
private_key에 비밀키(0x08859186d05abea0050739c323ae24d097bfb609ab1da13fbf7ed682421ea6ff)를 그대로 저장해도 코드 실행에는 문제가 없다. 그러나 비밀키는 혹시라도 노출되면 계좌의 자금이 전부 도난당할 수 있다. 따라서 비밀키를 그대로 입력하는 대신 다른 파일의 PRIVATE_KEY라는 변수에 비밀키를 입력하고 이를 함수를 통해 가져오는 방식을 채택했다. web3_py_simple_storage에 .env라는 파일을 추가하고 'export PRIVATE_KEY="0x08859186d05abea0050739c323ae24d097bfb609ab1da13fbf7ed682421ea6ff"'라는 코드를 .env에 추가하자.
git에 .env파일이 노출되지 않게 하기 위해 .gitignore라는 파일을 폴더에 추가해주고 '.env'를 파일 스크립트에 입력하자. 'git'이란 컴퓨터 파일의 변경 사항을 추적하고 이를 여러 사용자가 확인 및 조율할 수 있게 하는 분산 버전 관리 시스템이다. 즉, .gitignore을 통해 .env파일이 다른 git 참여자들에 의해 간섭받지 않도록 한다.

pip install python-dotenv

.env파일에서 데이터를 가져오기 위해 먼저 터미널로 python-dotenv library를 추가해주자.

from dotenv import load_dotenv
load_dotenv()

파이썬 코드 맨 위에 이를 추가하여 load_dotenv() 함수를 가져오고 실행하자. load_dotenv() 함수는 폴더에서 .env파일을 찾아 이를 스크립트에 가져온다. 이를 통해 'private_key = os.getenv("PRIVATE_KEY")'라는 아까 작성한 코드의 PRIVATE_KEY에 실제 비밀키가 입력된다. 즉, 스크립트에 비밀키를 명시하지 않고 비밀키를 사용할 수 있게 되었다.
위 일련의 과정을 통해 스크립트와 ganache 계좌를 연결하였다.

SimpleStorage = w3.eth.contract(abi=abi, bytecode=bytecode)
nonce = w3.eth.getTransactionCount(my_address)

위 함수의 우항은 계약을 생성하는 함수이다. 이때 abi, bytecode라는 매개변수에 우리가 기존에 설정한 abi, bytecode값을 넣어주고 있다. 이 함수의 반환값, 즉 계약을 SimpleStorage에 저장하고 있다.
계약을 실행, 즉 거래(transaction)를 생성하자. 거래 생성은 거래 구축, 거래 서명, 거래 전송의 세 과정으로 이루어진다.
'w3.eth.getTransactionCount(my_address)'은 주소를 입력값으로 받아 해당 주소의 난스(해당 주소의 거래 횟수)를 반환하는 함수이다.

transaction = SimpleStorage.constructor().buildTransaction(
    {
        "chainId": chain_id,
        "gasPrice": w3.eth.gas_price,
        "from": my_address,
        "nonce": nonce,
    }
)
signed_txn = w3.eth.account.sign_transaction(transaction, private_key=private_key)
tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)

SimpleStorage.constructor().buildTransaction은 Factory Method 패턴에서 SimpleStorage 형식의 인스턴스를 생성하고 buildTransaction이라는 함수를 실행한다. 이는 객체지향적 관점의 설명이고 개념적으로 하나하나 설명하려면 지나치게 오래 걸린다. 앞서 배운 개념으로 간단하게 말해서 계약 deploy를 시작한다. 이때 거래 정보를 중괄호 안에 담았다. 체인 id, gas Price, 거래 요청자(내 주소), 난스가 담겨있음이 확인된다.
이 거래 정보와 비밀키를 입력값으로 w3.eth.account.sign_transaction를 이용하여 거래를 서명하고 있다.
서명된 거래를 입력값으로 w3.eth.send_raw_transaction를 실행하면 거래 전송이 이루어진다.
코드 마지막줄은 거래 전송을 통해 생성된 해시를 입력값으로 하여 채굴로 거래가 완전히 deploy될 때까지 기다리고, 완료 정보를 tx_receipt에 담고 있다.
Python 스크립트를 실행하고 ganache에 들어가보면 transaction이 생성되었음을 확인할 수 있다.

이제 deploy된 contract로 작업해보자! 작업을 위해서는 deploy된 contract의 주소와 abi, 이 2가지 정보가 필요하다.

simple_storage = w3.eth.contract(address=tx_receipt.contractAddress, abi=abi)

simple_storage에 우리가 deploy한 계약을 저장하고 있다. 이때 입력값인 주소 정보는 tx_receipt.contractAddress에서 얻을 수 있고, abi는 아까와 동일하다.
deploy된 계약의 함수는 call function과 transact function으로 나눌 수 있다. call function은 Remix에서 파란색 버튼으로 표현된 함수로, 단순히 값을 반환한다. transact function은 거래를 생성하는 함수로, 주황색(송금과 관련되면 빨간색)으로 표현되었다. Python은 코드로 call과 transact를 하기 때문에 call function을 transact할 수도, transact function을 call할 수도 있다.(굳이 그럴 필요는 없다.)

print(simple_storage.functions.retrieve().call())

greeting_transaction = simple_storage.functions.store(15).buildTransaction(
{
        "chainId": chain_id,
        "gasPrice": w3.eth.gas_price,
        "from": my_address,
        "nonce": nonce + 1,
}
)
signed_greeting_txn = w3.eth.account.sign_transaction(
    greeting_transaction, private_key=private_key
)
tx_greeting_hash = w3.eth.send_raw_transaction(signed_greeting_txn.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_greeting_hash)

print(simple_storage.functions.retrieve().call())

첫줄은 아까 저장한 계약인 simple_storage에서 retreive() 함수를 call하고 그 반환값을 출력하고 있다. 아직 favoriteNumber에 저장된 값이 없으므로 0이 출력된다. 다음으로 거래를 생성하고 있음을 확인하고 있다. store 함수에 15를 저장하는 거래임을 알 수 있고, nonce가 simple_storage를 구축하는데 활용되었으므로 nonce에 1을 더해주고 있다.
다시 retrieve 함수를 call하면 store()를 통해 favoriteNumbers에 저장된 15가 출력됨을 확인할 수 있다.
ganache에서 생성한 거래가 확인된다.

마지막으로 지금까지 작성한 코드를 조금 변형하여 Rinkeby Network에서 거래를 실행해보자. 이를 위해 Infura라는 사이트를 이용하자. Infura는 이더리움 노드 대여를 통해 사용자들이 간편하게 이더리움 네트워크를 이용할 수 있게 해준다. 아래 링크로 들어가자.
https://infura.io
들어가서 회원가입을 하고 'CREATE NEW KEY'를 눌러 아래와 같이 Web3 API로 네트워크를 설정한 후 CREATE을 눌러 KEY를 생성하자.

다음 나오는 창에서 RINKEBY 네트워크 이용을 위해 Mainnet으로 설정되어 있는 이더리움 NETWORK ENDPOINT를 아래 사진과 같이 RINKEBY로 변경해주자

w3 = Web3(Web3.HTTPProvider("https://rinkeby.infura.io/v3/a9c5bc0ec75c4a83a3e48086df81acfe"))
chain_id = 4
my_address = "0x00d589FF3581970c0b3BB283b7985c7b3Bc0D18f"
private_key = os.getenv("PRIVATE_KEY")

이전에 ganache와의 연결을 위해 설정했던 코드를 위와 같이 Infura에서 제공하는 Rinkeby RPC URL과 Metamask 기반으로 바꾸어주자. 그 후 .env에 저장되어 있는 비밀키를 Metamask 계좌의 비밀키로 바꾸어주자.
파이썬 스크립트를 실행하고 Rinkeby Etherscan에 들어가 계좌 검색을 해보면 다음과 같이 거래가 생성되었음을 확인할 수 있다.

얼른 전역하고 싶다! 정말로!! 마니마니!!! 장기휴가까지 4주도 안 남았다. 오늘 하루도 고생한 우리 모두에게 칭찬의 박수와 격려를 보낸다. 높아진 난이도의 포스팅을 잘 마무리하게 해주신 하나님 아버지께 감사하며 잠에 들겠다. 다음 포스팅에서 보자!!!!! ㅎㅎㅎㅎㅎㅎㅎ

Github Code License:

MIT License

Copyright (c) 2021 SmartContract

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
profile
BlockChain Researcher
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 7월 31일

ganache를 설치하지 못하는게 너무 아쉽습니다.
하지만 어떻게든 공부를 이어나가는 열정에 박수를 보냅니다. 짝짝짝.

답글 달기