스마트 컨트랙트(Smart Contract)에 대한 서명(VRS) 코딩 실습
2021. 10. 15. 16:43ㆍBlockchain/Truffle
실습
- truffle unbox react
- ganache 실행 후 metamask 연결
- truffle-config.js 내용 수정 // 이전과 같이
- /contracts/SimpleStorage.sol 코드 수정
// SPDX-License-Identifier: MIT pragma solidity >=0.4.21 <0.7.0; contract SimpleStorage { uint storedData; event Change(string message, uint newVal); // 추가 function set(uint x) public { storedData = x; emit Change('set', x); // 추가 } function get() public view returns (uint) { return storedData; } }
- truffle compile
- truffle migrate
- /client/src/App.js 코드 수정
import React, { useState } from "react"; import SimpleStorageContract from "./contracts/SimpleStorage.json"; import getWeb3 from "./getWeb3"; import "./App.css"; const App = () => { const [value, setValue] = useState(0); const [storage, setStorage] = useState(0); const [loading, setLoading] = useState(false); // 직접 서명 const send = () => { } // DB(Back-end) 거치고 서명 const sendAPI = () => { } // DB 서명 const sendTx = () => { } const handleChange = e => { const val = e.target.value; setValue(val); } return ( <div style={{padding:"20px"}}> <input type="text" value={value} onChange={handleChange} /> <div> <button onClick={send}>직접 서명</button> <button onClick={sendAPI}>DB 거치고 서명</button> <button onClick={sendTx}>DB 서명</button> </div> <div> {loading ? '로딩 중' : storage} </div> </div> ); } export default App;
- npm install @truffle/contract // 서버 종료 후 패키지 설치
- 로딩 상태 변수로 선언하여 처리, 설치 한 패키지 가져오기, Web3와 contract 연결
(이전 포스트 참조, 링크 : https://hwan91.tistory.com/12)
import React, { useEffect, useState } from "react"; import SimpleStorageContract from "./contracts/SimpleStorage.json"; import getWeb3 from "./getWeb3"; import "./App.css"; const App = () => { const [value, setValue] = useState(0); const [storage, setStorage] = useState(0); const [loading, setLoading] = useState(false); const handleResult = () => { setStorage(value); setLoading(prev => !prev); } // 직접 서명 const send = () => { if (value > 0) { setLoading(prev => !prev); handleResult(); // 비동기적 처리 } } // DB(Back-end) 거치고 서명 const sendAPI = () => { if (value > 0) { setLoading(prev => !prev); handleResult(); // 비동기적 처리 } } // DB 서명 const sendTx = () => { if (value > 0) { setLoading(prev => !prev); handleResult(); // 비동기적 처리 } } const handleChange = e => { const val = e.target.value; setValue(val); } const init = async () => { const contract = require('@truffle/contract'); const web3 = await getWeb3(); const [account] = await web3.eth.getAccounts(); // const networkId = await web3.eth.net.getId(); // network id값 가져오기, 참고 let simpleStorage = contract(SimpleStorageContract); simpleStorage.setProvider(web3.currentProvider); const instance = await simpleStorage.deployed(); console.log(web3); console.log(account); console.log(instance); } useEffect(() => { init(); }, []); return ( <div style={{padding:"20px"}}> <input type="text" value={value} onChange={handleChange} /> <div> <button onClick={send}>직접 서명</button> <button onClick={sendAPI}>DB 거치고 서명</button> <button onClick={sendTx}>DB 서명</button> </div> <div> {loading ? '로딩 중' : storage} </div> </div> ); } export default App;
- reducer와 dispatch를 사용하여 상태 관리
import React, { useEffect, useReducer, useState } from "react"; import SimpleStorageContract from "./contracts/SimpleStorage.json"; import getWeb3 from "./getWeb3"; import "./App.css"; const reducer = (state, action) => { switch (action.type) { case "INIT": let { web3, instance, account } = action; return { ...state, web3, instance, account, } } } const INIT_ACTIONS = (web3, instance, account) => { return { type: 'INIT', web3, instance, account, } } const App = () => { const initialState = { web3: null, instance: null, account: null, } const [value, setValue] = useState(0); const [storage, setStorage] = useState(0); const [loading, setLoading] = useState(false); const [state, dispatch] = useReducer(reducer, initialState); const handleResult = () => { setStorage(value); setLoading(prev => !prev); } // 직접 서명 const send = () => { if (value > 0) { setLoading(prev => !prev); handleResult(); // 비동기적 처리 } } // DB(Back-end) 거치고 서명 const sendAPI = () => { if (value > 0) { setLoading(prev => !prev); handleResult(); // 비동기적 처리 } } // DB 서명 const sendTx = () => { if (value > 0) { setLoading(prev => !prev); handleResult(); // 비동기적 처리 } } const handleChange = e => { const val = e.target.value; setValue(val); } const init = async () => { const contract = require('@truffle/contract'); const web3 = await getWeb3(); const [account] = await web3.eth.getAccounts(); // const networkId = await web3.eth.net.getId(); // network id값 가져오기, 참고 let simpleStorage = contract(SimpleStorageContract); simpleStorage.setProvider(web3.currentProvider); const instance = await simpleStorage.deployed(); // console.log(web3); // console.log(account); // console.log(instance); dispatch(INIT_ACTIONS(web3, instance, account)); } useEffect(() => { init(); }, []); return ( <div style={{padding:"20px"}}> <input type="text" value={value} onChange={handleChange} /> <div> <button onClick={send}>직접 서명</button> <button onClick={sendAPI}>DB 거치고 서명</button> <button onClick={sendTx}>DB 서명</button> </div> <div> {loading ? '로딩 중' : storage} </div> </div> ); } export default App;
- 직접 서명에 대한 코드 작성
콘솔 출력 테스트 // 5000 입력, 직접 서명 버튼 클릭 후 MetaMaskNontification에서 확인import React, { useEffect, useReducer, useState } from "react"; import SimpleStorageContract from "./contracts/SimpleStorage.json"; import getWeb3 from "./getWeb3"; import "./App.css"; const reducer = (state, action) => { switch (action.type) { case "INIT": let { web3, instance, account } = action; return { ...state, web3, instance, account, } } } const INIT_ACTIONS = (web3, instance, account) => { return { type: 'INIT', web3, instance, account, } } const App = () => { const initialState = { web3: null, instance: null, account: null, } const [value, setValue] = useState(0); const [storage, setStorage] = useState(0); const [loading, setLoading] = useState(false); const [state, dispatch] = useReducer(reducer, initialState); const handleResult = (log, web3) => { // data : 0x0000000000000000000 // decodeLog의 인자값 >> 1: 데이터 형식(params), 2: log.data const params = [ // .sol 파일에 작성했던 event Change의 인자값 내용과 동일 { type: 'string', name: 'message', }, { type: 'uint256', name: 'newVal', }, ]; const returnValues = web3.eth.abi.decodeLog(params, log.data); console.log(returnValues); // case가 많을 경우 switch문으로 작성 if (returnValues.message == 'set') { setStorage(value); setLoading(prev => !prev); } } // 직접 서명 const send = async () => { const { account, instance } = state; if (value > 0) { setLoading(prev => !prev); await instance.set(value, { from: account }); // handleResult(); // 비동기적 처리 } } // DB(Back-end) 거치고 서명 const sendAPI = () => { if (value > 0) { setLoading(prev => !prev); // handleResult(); // 비동기적 처리 } } // DB 서명 const sendTx = () => { if (value > 0) { setLoading(prev => !prev); // handleResult(); // 비동기적 처리 } } const handleChange = e => { const val = e.target.value; setValue(val); } const init = async () => { const contract = require('@truffle/contract'); const web3 = await getWeb3(); const [account] = await web3.eth.getAccounts(); // const networkId = await web3.eth.net.getId(); // network id값 가져오기, 참고 let simpleStorage = contract(SimpleStorageContract); simpleStorage.setProvider(web3.currentProvider); const instance = await simpleStorage.deployed(); // console.log(web3); // console.log(account); // console.log(instance); dispatch(INIT_ACTIONS(web3, instance, account)); // websocket 사용과 비슷 web3.eth.subscribe('logs', { address: instance.address }) // 배포가 된, contract 계정의 address .on('data', log => { console.log(log); handleResult(log, web3); }) .on('error', err => { console.log(err); }) } useEffect(() => { init(); }, []); return ( <div style={{padding:"20px"}}> <input type="text" value={value} onChange={handleChange} /> <div> <button onClick={send}>직접 서명</button> <button onClick={sendAPI}>DB 거치고 서명</button> <button onClick={sendTx}>DB 서명</button> </div> <div> {loading ? '로딩 중' : storage} </div> </div> ); } export default App;
- npm install axios // 다음 작업에 필요한 패키지 설치 후 import로 가져오기
- root 디렉토리에서 npm init
root 디렉토리에서 server 폴더 생성, 그 안에 아래와 같은 구조로 폴더 및 파일 생성 - /server/server.js 코드 작성
const express = require('express'); const app = express(); const bodyParser = require('body-parser'); const cors = require('cors'); const router = require('./routes'); app.use(cors()); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.use('/', router); app.listen(3001, () => { console.log(`server on port 3001`); });
- /server/routes/index.js 코드 작성
const express = require('express'); const router = express.Router(); const rpc = require('./rpc'); router.use('/rpc', rpc); module.exports = router;
- /server/routes/rpc/index.js 코드 작성
const express = require('express'); const router = express.Router(); const controller = require('./rpc_controller'); router.post('/set', controller.set); router.post('/setTx', controller.setTx); module.exports = router;
- /server/routes/rpc/rpc_controller.js 코드 작성
const Web3 = require('web3'); const web3 = new Web3('http://localhost:8545'); const { abi } = require('../../../client/src/contracts/SimpleStorage.json'); const { address } = require('../../../client/src/contracts/SimpleStorage.json').networks['5777']; const ethTx = require('ethereumjs-tx').Transaction; // Back-end 거치고 서명 const set = async (req, res) => { const { from, val } = req.body; try { const contract = await new web3.eth.Contract(abi, address); const data = await contract.methods.set(val).encodeABI(); console.log('data : ', data); let txObject = { // transaction == tx from, to: address, // contract address >> /client/src/contracts/SimpleStorage.json의 networks 객체안에 5777 안에 address.. 길어서 전역 변수로 뺌 data, // 배포한 set 메서드를 인자값을 넣은 set(10)으로 바꿔줘야함 >> set 메서드 사용을 위해 web3 사용 gasLimit: web3.utils.toHex(30000), // hex(16진수)로 변환 gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')), // wei 단위로 작성 후 hex로 변환 } res.json({ // msg: 'hello set', success: true, raxTx: txObject, }); } catch (e) { console.log(e); res.json({ success: false, }); } } // Back-end 서명 const setTx = async (req, res) => { const { from, val } = req.body; const privateKey = Buffer.from('fae7534c013c508e384b4d912bf258ea0ebdd18a636eef5a736f975b55c41d90', 'hex'); // '가나슈 0번 인덱스의 privatekey, + hex로 반환' try { const instance = await new web3.eth.Contract(abi, address); const data = await instance.methods.set(val).encodeABI(); const txCount = await web3.eth.getTransactionCount(from); console.log('data : ', data); let txObject = { nonce: web3.utils.toHex(txCount), from, to: address, data, gasLimit: web3.utils.toHex(30000), gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')), } // 서명 시 개인키가 필요, 따라서 비밀키(privateKey)가 필요 const tx = new ethTx(txObject); console.log(tx); tx.sign(privateKey); const serializedTx = tx.serialize(); console.log(serializedTx.toString('hex')); const txhash = await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')); res.json({ // msg: 'hello setTx', success: true, txhash, }); } catch (e) { console.log(e); res.json({ success: false, }); } } module.exports = { set, setTx, }