|
| 1 | +--- |
| 2 | +title: 스마트 컨트랙트 실행하기 |
| 3 | +date: 2024-12-16 12:22:00 +/-TTTT |
| 4 | +categories: [Security, Blockchain] |
| 5 | +tags: [security, blockchain, geth, ethereum, smart-contract, troubleshooting] |
| 6 | +math: true |
| 7 | +--- |
| 8 | + |
| 9 | +## Solidity Compiler 설치하기 |
| 10 | + |
| 11 | +npm을 이용하여 설치해보자. |
| 12 | + |
| 13 | +```shell |
| 14 | +% sudo npm install -g solc |
| 15 | +``` |
| 16 | + |
| 17 | +## 프로그래밍 예제 |
| 18 | + |
| 19 | +> VSC에서 `hello.sol` 파일을 만들고 프로그래밍 해보자. |
| 20 | +
|
| 21 | +``` |
| 22 | +// SPDX-License-Identifier: UNLICENSED |
| 23 | +pragma solidity 0.4.24; |
| 24 | +
|
| 25 | +contract hello{ |
| 26 | + string message = "Hello world!"; |
| 27 | + function showMsg() public view returns (string memory){ |
| 28 | + return message; |
| 29 | + } |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +## 컴파일 |
| 34 | + |
| 35 | +> npm을 이용하여 설치해주었기 때문에, 기본 명령어가 `solc` 가 아닌 `solcjs`이다! |
| 36 | +{: .prompt-info} |
| 37 | + |
| 38 | +```shell |
| 39 | +% solcjs --abi --bin hello.sol |
| 40 | +``` |
| 41 | + |
| 42 | +성공적으로 컴파일이 되면 해당 폴더 내 .abi, .bin파일이 생성된다. |
| 43 | + |
| 44 | +- abi (Application Binary Interface) : 스마트 컨트랙트 코드의 설명이 담긴 JSON 파일 |
| 45 | + - 어떤 함수가 들어있는지 설명하는 것. |
| 46 | +- bin : 컴파일 된 바이너리 파일 |
| 47 | + |
| 48 | +## 실행하기 |
| 49 | + |
| 50 | +### 콘솔 접속 |
| 51 | + |
| 52 | +먼저, 미리 만들어둔 사설 네트워크의 콘솔에 접속한다. |
| 53 | + |
| 54 | +> 필자는 포트가 다른 프로세스와 겹쳐 임시로 30000으로 설정하였다. |
| 55 | +
|
| 56 | +```shell |
| 57 | +geth --datadir cslab --port 30000 --nodiscover console |
| 58 | +``` |
| 59 | + |
| 60 | +콘솔에서 `bin`, `abi` 변수를 각각 설정한다. |
| 61 | +- `bin` : 컴파일된 bin파일을 설정해줌. |
| 62 | +- `abi` : json파일 객체 그대로 설정. |
| 63 | + |
| 64 | +### 변수 설정 |
| 65 | + |
| 66 | +> bin 변수 설정 |
| 67 | +
|
| 68 | +- bin파일을 그대로 복사하여 앞에 16진수를 의미하는 0x를 붙여 변수로 설정한다. |
| 69 | +- 또한 문자열을 의미하는 "" 따옴표로 감싸서 변수 설정을 해준다. |
| 70 | +```shell |
| 71 | +> bin = "0x{컴파일된 16진수 bin파일 내용}" |
| 72 | +``` |
| 73 | + |
| 74 | + |
| 75 | +> abi 변수 설정 |
| 76 | +
|
| 77 | +- bin과 다르게 string 문자열이 아닌 객체 형태로 바로 설정한다. |
| 78 | + |
| 79 | +```shell |
| 80 | +> abi = [{"inputs":[],"name":"showMsg","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}] |
| 81 | + |
| 82 | +// 결과 |
| 83 | +[{ |
| 84 | + inputs: [], |
| 85 | + name: "showMsg", |
| 86 | + outputs: [{ |
| 87 | + internalType: "string", |
| 88 | + name: "", |
| 89 | + type: "string" |
| 90 | + }], |
| 91 | + stateMutability: "view", |
| 92 | + type: "function" |
| 93 | +}] |
| 94 | +``` |
| 95 | + |
| 96 | +### 이더리움에서 실행하기 |
| 97 | + |
| 98 | +`sendTransaction()` 함수는 주소를 반환한다. |
| 99 | +- 여기서는 `tx`라는 변수에 주소를 저장한다. |
| 100 | + |
| 101 | +```shell |
| 102 | +> tx = eth.sendTransaction({from: eth.accounts[0], data: bin}) |
| 103 | +``` |
| 104 | + |
| 105 | +<br> |
| 106 | + |
| 107 | +하지만 위 명령어를 실행하면 다음과 같이 Authentication 에러가 발생한다. |
| 108 | + |
| 109 | +{: w="400" }{: .shadow} |
| 110 | + |
| 111 | +그러므로, 0번 계좌의 비밀번호를 풀어주는 과정이 필요하다. |
| 112 | + |
| 113 | +```shell |
| 114 | +> personal.unlockAccount(eth.accounts[0]) |
| 115 | +``` |
| 116 | + |
| 117 | +그리고 다시 `sendTransaction()` 을 실행시켜주면! 성공적으로 실행된다. |
| 118 | + |
| 119 | +### 채굴 |
| 120 | + |
| 121 | +하지만 현재는 바이트코드가 블록에는 들어갔으나, 다른 블록체인과 연결되지는 못한 상태이다. |
| 122 | + |
| 123 | +```shell |
| 124 | +> eth.getTransactionReceipt(tx) |
| 125 | +null |
| 126 | +``` |
| 127 | + |
| 128 | +따라서 거래는 되었지만 블록체인에 연결이 되지 않아 결과값이 `null`인 것을 확인할 수 있다. |
| 129 | + |
| 130 | +```shell |
| 131 | +> miner.start() |
| 132 | +> miner.stop() |
| 133 | +``` |
| 134 | + |
| 135 | +위 명령어를 통해 채굴을 시작하면 블록체인에 연결된다. 몇 초 기다린 뒤, stop() 해주도록 하자. |
| 136 | + |
| 137 | +그리고 다시 receipt(부가 트랜잭션 정보)를 확인하면? 다음과 같은 결과값을 얻을 수 있다. |
| 138 | + |
| 139 | +```shell |
| 140 | +> eth.getTransactionReceipt(tx) |
| 141 | +``` |
| 142 | + |
| 143 | +```json |
| 144 | +{ |
| 145 | + blockHash: "0xa49a284293d151db5b61f8cf2b50b4fc61d58e01a46b1a6ea6f10ecebf040e9f", |
| 146 | + blockNumber: 7, |
| 147 | + contractAddress: "0x71776219f07d03224d3c615a6fc22ad450bb3753", |
| 148 | + cumulativeGasUsed: 3925000, |
| 149 | + from: "0xc71a5b838cf49fd8bd7db6e1493ae0222f8acf3c", |
| 150 | + gasUsed: 300000, |
| 151 | + logs: [], |
| 152 | + logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
| 153 | + status: "0x0", |
| 154 | + to: null, |
| 155 | + transactionHash: "0xaaa6c78db34de837020fb61cbfb02c4e64c75b0f745b07b52a9caac32cba4f6f", |
| 156 | + transactionIndex: 3 |
| 157 | +} |
| 158 | +``` |
| 159 | + |
| 160 | +특히 이 결과값에서 `contractAddress`를 주로 사용하게 될 건데, <br>`contractAddress`란? 바이너리 코드가 들어간 위치(**스마트 컨트랙트가 들어간 블록의 주소**)이다. |
| 161 | + |
| 162 | +아래 명령어를 통해 contractAddress를 address라는 변수에 저장해주도록 하자. |
| 163 | + |
| 164 | +```shell |
| 165 | +> address = eth.getTransactionReceipt(tx).contractAddress |
| 166 | +"0x71776219f07d03224d3c615a6fc22ad450bb3753" |
| 167 | +``` |
| 168 | + |
| 169 | +### 계약 인스턴스 가져오기 |
| 170 | + |
| 171 | +스마트 컨트랙트를 다음과 같이 배포하고, eth.contract()를 사용하여 계약 인스턴스를 가져오고 메서드들을 호출할 수 있다. |
| 172 | + |
| 173 | +```shell |
| 174 | +> contractInterface = eth.contract(abi).at(address) |
| 175 | +``` |
| 176 | + |
| 177 | +```json |
| 178 | +{ |
| 179 | + abi: [{ |
| 180 | + inputs: [], |
| 181 | + name: "showMsg", |
| 182 | + outputs: [{...}], |
| 183 | + stateMutability: "view", |
| 184 | + type: "function" |
| 185 | + }], |
| 186 | + address: "0x71776219f07d03224d3c615a6fc22ad450bb3753", |
| 187 | + transactionHash: null, |
| 188 | + allEvents: function(), |
| 189 | + showMsg: function() |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +이 내용은 위에서 작성한 `hello.sol` 코드의 내용이다. 여기서 우리가 작성한 showMsg() 함수를 실행해보자. |
| 194 | + |
| 195 | +### 함수 실행 |
| 196 | + |
| 197 | +```shell |
| 198 | +> contractInterface.showMsg.call() |
| 199 | +"Hello world!" |
| 200 | +``` |
| 201 | + |
| 202 | +## 트러블 슈팅 |
| 203 | + |
| 204 | +### `Error: intrinsic gas too low` |
| 205 | + |
| 206 | +아래와 같은 에러가 발생한다면 배포하거나 호출하는 트랜잭션에서 가스 한도를 너무 적게 설정했을 가능성이 크다! |
| 207 | + |
| 208 | +```shell |
| 209 | +> tx = eth.sendTransaction({from: eth.accounts[0], data: bin}) |
| 210 | +Error: intrinsic gas too low |
| 211 | + at web3.js:3143:20 |
| 212 | + at web3.js:6347:15 |
| 213 | + at web3.js:5081:36 |
| 214 | + at <anonymous>:1:6 |
| 215 | +``` |
| 216 | + |
| 217 | +다음과 같이 `gas:3000000` 충분한 가스를 할당할 경우 해결할 수 있다. |
| 218 | + |
| 219 | +```shell |
| 220 | +> tx = eth.sendTransaction({from: eth.accounts[0], data: bin, gas:3000000}) |
| 221 | +INFO [12-16|18:23:07.052] Submitted contract creation fullhash=0x7ec5f48701c964fc7563fa5f2dcc52b00f8ad5298942d444f6ce297eeac6c066 contract=0x8EC59dabc0d88eAbeed9cC869B81795Ac321673d |
| 222 | +"0x7ec5f48701c964fc7563fa5f2dcc52b00f8ad5298942d444f6ce297eeac6c066" |
| 223 | +``` |
| 224 | + |
| 225 | +#### 여기서 가스란? |
| 226 | + |
| 227 | +Gas(가스)는 이더리움이라는 거대한 컴퓨터를 쓰기 위한 일종의 연료이다. |
| 228 | +- 트랜잭션을 수행하는데에 있어 네트워크에 대한 **수수료**이다. |
| 229 | + |
| 230 | +1. 스마트 컨트랙트를 배포할 때 |
| 231 | +2. 함수에서 상태 변수에 변화를 줄 때 |
| 232 | + |
| 233 | +등등 컨트랙트 내부에서 특정 코드를 실행할 때 발생되는 수수료이다. |
| 234 | + |
| 235 | + |
| 236 | +#### 해결되지 않은 의문..... |
| 237 | + |
| 238 | +가스 계산이 잘못된 것 같다. 내 코드에서는 상태 변수에 변화도 없고, 아주 간단하여 복잡성도 적다.. |
| 239 | + |
| 240 | +뭔가 세팅에서 잘못된 것 같은데.. 풀리지 않았다 |
| 241 | + |
| 242 | +```shell |
| 243 | +> eth.getTransactionReceipt(tx) |
| 244 | +{ |
| 245 | + blockHash: "0xbb1dcbcf02bd1cdc064bfe6027e5021cc80ae9e542282091f9b58297f82f2976", |
| 246 | + blockNumber: 58, |
| 247 | + contractAddress: "0xa9d6fe280d6fbe013726e4f6cfb44871ff2d34b9", |
| 248 | + cumulativeGasUsed: 199384, |
| 249 | + from: "0xc71a5b838cf49fd8bd7db6e1493ae0222f8acf3c", |
| 250 | + gasUsed: 199384, |
| 251 | + logs: [], |
| 252 | + logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
| 253 | + status: "0x1", |
| 254 | + to: null, |
| 255 | + transactionHash: "0x587427e8b4ada7cae90cc17dbb1e265df6bb3d5770fdb32db707f89dfbcc7a5e", |
| 256 | + transactionIndex: 0 |
| 257 | +} |
| 258 | +``` |
| 259 | + |
| 260 | +이 트랜잭션에서는 199384의 gas를 사용하였는데, 왜이렇게 많이 사용했을까? 아니면 이게 일반적인 수치인가? |
| 261 | + |
| 262 | +공부가 더 필요하다.. |
| 263 | + |
| 264 | +### Error: new BigNumber() not a base 16 number: |
| 265 | + |
| 266 | +```shell |
| 267 | +> contractInterface.showMsg.call() |
| 268 | +Error: new BigNumber() not a base 16 number: |
| 269 | + at L (bignumber.js:3:2876) |
| 270 | + at bignumber.js:3:8435 |
| 271 | + at a (bignumber.js:3:389) |
| 272 | + at web3.js:1110:23 |
| 273 | + at web3.js:1634:20 |
| 274 | + at web3.js:826:16 |
| 275 | + at map (<native code>) |
| 276 | + at web3.js:825:12 |
| 277 | + at web3.js:4080:18 |
| 278 | +``` |
| 279 | + |
| 280 | +뜬금없이 함수 호출할 때, 다음과 같이 `Error: new BigNumber() not a base 16 number: ` 에러가 발생하였다. |
| 281 | + |
| 282 | +구글링해보니 web3.js 버그.. 배포 주소 버그.. 등등에 관련한 질문이 나왔는데 **결과적으로는 solidity 컴파일러 버전과 geth 버전의 차이때문에 발생한 문제였다.** |
| 283 | + |
| 284 | +geth 버전은 공부하고 있는 책의 버전 대로 `1.8.13-stable`을, 하지만 solidity 컴파일러는 `0.8.x`대의 최신 버전을 사용하여 발생한 에러였다. |
| 285 | + |
| 286 | +```bash |
| 287 | +% npm uninstall solc |
| 288 | +% npm install -g solc@0.4.24 |
| 289 | +``` |
| 290 | + |
| 291 | +위의 명령어를 통해 컴파일러를 삭제하고, geth 버전에 맞춘 `0.4.24` 버전으로 다운그레이드하여 해결하였다. |
| 292 | + |
| 293 | +물론, solidity 코드도 `pragma solidity = 0.4.24;` 로 변경하여 특정 버전의 컴파일러를 선택하도록하였다. |
| 294 | + |
| 295 | +Thanks to.. [힌트 주신 하나의 게시글](https://www.clien.net/service/board/cm_blockchain/13645133) |
0 commit comments