-> 지난 블록체인 Block-Chain - 이더리움 DApp - Counter 만들기 ( Feat. Ganache ) (1) 포스트에서 이어집니다.
Ganache network
를 재실행하게 되면 배포한Smart Contract
가 초기화되기 때문에 자동으로json
파일의network
에 배포된CA
값을 가져오도록 세팅하였습니다.migration --reset
으로 컴파일 내용을 재배포 한뒤FrontServer
의 기존의json
파일을 제거하고 재배포한json
파일을 가져와주었습니다.
web3.eth.net.getId()
: 현재 연결된 네트워크의 ID를 가져옵니다.ID
를 가지고 Counter.json
파일에서 Contract Address
값을 가져옵니다.
SmartContract Event
란SolidityCode
로 작성한Contract
의 함수들이 호출될 때 배포하고 난뒤TransactionReceipt
에 해당 기록을 남김으로써 후에 기록을 찾아볼 수 있도록log
를 삽입하는 것입니다.Event
를 등록하는 이유는 현재 코드에서+
버튼을 누르는 사람이A
라고 했을때A
의 화면에는 바뀐count
값을 가져와서 화면에 띄워주겠지만B
라는 사람의 화면에는 상태변수가 변했을때 다시 바뀐count
값을 가져와서 화면에 띄워주는 코드가 없습니다.
즉 본인이+
버튼을 눌렀을 경우에는 화면에 바로 바뀐 값이 렌더되겠지만 본인이+
버튼을 누르지 않았을때에는network
상에서는count
값이 변했겠지만 이 변한 값을 받아오는 부분이 빠져있기 때문에Event
를 추가하여 본인이+
버튼을 누르지 않아도log
를 추적하여 새로운log
가 발생되면 즉시 해당log
를 읽어 값을 변경하여 내가 버튼을 누르지 않아도 화면에 바뀐 상태변수를 보여주도록 할 것입니다.
Solidity Code
수정 contract Counter {
uint256 private _count;
// 이벤트 선언 => 이벤트명은 Count이고 uint256에 대한 내용으로 log를 찍을것입니다.
event Count(uint256 count);
// public이 아닌 private기 때문에 _count 상태변수를 가져오는 함수를 만들어주었습니다.
function current() public view returns (uint256) {
return _count;
}
function increment() public {
_count += 1;
// 이벤트를 실행 => log를 찍음
emit Count(_count);
}
function decrement() public {
_count -= 1;
// 이벤트를 실행 => log를 찍음
emit Count(_count);
}
}
Count Event
추가 및 함수 실행시 emit을 사용하여 Count Event를 실행하도록 하였습니다.Solidity Code
를 수정했으니 재배포 하고Counter.json
파일을 다시FrontServer
로 가져와야 합니다.truffle migration --reset
후 배포한json
파일FrontServer
로 가져옵시다.
useEffect(() => {
(async () => {
if (deployed) return;
// netwokrId 가져오기
const networkId = await web3.eth.net.getId();
// Counter.json 파일에서 network의 CA 가져오기
const ContractAddress = CounterContract.networks[networkId].address;
// 2가지 인자값 abi ,CA
const deploy = await new web3.eth.Contract(CounterContract.abi, ContractAddress);
const current = await deploy.methods.current().call();
// logs의 두번째 인자값의 내용은 어디 Contract의 log를 가져올 것인가 CA값을 적습니다.
web3.eth
.subscribe('logs', { address: ContractAddress }) //
.on('data', (log) => {
// on 메서드로 연결한 CA에서 data를 받을때 log를 console.log로 찍을것입니다.
console.log(log);
});
setDeployed(deploy);
setCounte(current);
})();
}, []);
Front
에서useEffect
부분에log
를 읽는 코드를 추가합니다.+
혹은-
버튼을 누르고console.log(log)
를 확인해보면 아래와 같은 값이 출력됩니다.data
값에 해당 숫자가 찍힌것을 확인할 수 있는데0x0000...2
이런식으로 나오는 이유는Contract
를 작성할 때count
를uint256
으로 타입을 선언해주었기 때문에 메모리 공간을uint256
만큼 공간을 만들어주었기 때문입니다.
web3.eth.subscribe()
코드 수정 web3.eth
.subscribe('logs', { address: ContractAddress }) //
.on('data', (log) => {
// on 메서드로 연결한 CA에서 data를 받을때 log를 console.log로 찍을것입니다.
console.log(log.data);
// 첫번째 인자값은 16진수로 받아온 log를 어떤 type으로 파싱을 할것인가와 파싱한값을 어떤 name으로 받을것인가를 적습니다.
// 두번째 인자값은 파싱할 데이터가 들어갑니다.
const value = web3.eth.abi.decodeLog([{ type: 'uint256', name: '_count' }], log.data);
// return Obj 값이 객체로 나오는 이유는 Event에서 여러개의 data를 넣는다면 여러개의 data값이 출력되기 때문에 객체 형태로 return 됩니다.
console.log(value);
});
- 위와 같이
log.data
를decodeLog
메서드를 사용하여 16진수에서 사람이 읽을 수 있도록 파싱을 하면Object
로 아래와 같은 값이 나옵니다.
- 객체의
0
은data
값의index
를 의미하고length
는 길이_count
는 파싱한data
값을 의미합니다.
web3.eth
.subscribe('logs', { address: ContractAddress }) //
.on('data', (log) => {
// on 메서드로 연결한 CA에서 data를 받을때 log를 console.log로 찍을것입니다.
console.log(log.data);
// 첫번째 인자값은 16진수로 받아온 log를 어떤 type으로 파싱을 할것인가와 파싱한값을 어떤 name으로 받을것인가를 적습니다.
// 두번째 인자값은 파싱할 데이터가 들어갑니다.
const value = web3.eth.abi.decodeLog([{ type: 'uint256', name: '_count' }], log.data); // return Obj 값이 객체로 나오는 이유는 Event에서 여러개의 data를 넣을수도 있기 때문에
console.log(value);
setCounte(value._count);
});
--- 중략 ---
const increment = async () => {
await deployed.methods.increment().send({ from: account });
};
const decrement = async () => {
await deployed.methods.decrement().send({ from: account });
};
- 마지막으로 위와 같이 이벤트
log
를 추적하여 받은data
값을 파싱하고 이 파싱한count
값을setCounte
로 변경하면 내가 아닌 다른 누군가가+
버튼을 눌러도 내 화면에서도event
의log
를 받기 때문에 내 화면에도count
가 변경될 것입니다.- 내가
+
혹은-
버튼을 누른다고 하더라도subscribe
메서드로log
를 추적할 것이니 내 화면에도count
가 변경될 것입니다.
- 이런식으로
Transaction
이 발생되었을때 블록이mining
이 되며Smart Contract
가 실행되고 이때Event
가 발동하여log
를 남길 경우TransactionReceipt
의logs
부분에 해당log
의 내용들을 확인할 수 있습니다.
- 지금까지는
Front
에서MetaMask
와 연결하여SmartContract
를 실행시켰다면 이제는Back
서버에서 서명을 제외한Transaction
을 생성하여Front
로 전달한후Front
에서 해당Transaction
을 가지고MetaMask
에Transaction
을 보내도록 처리할 것입니다.- 이렇게 작업해야하는 이유는 지금과 같이
Counter
같이 간단한 작업들은Front
에서 처리해도 되지만, 예를 들어 쇼핑몰 같은 경우에DataBase
에 연동하여 작업을 해야할 경우가 있기 때문에 미리 연습해 보는것입니다.
Back
서버 세팅/* server.js */
const express = require('express');
const app = express();
const cors = require('cors');
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545')); // Ganache 네트워크 연결
app.use(
cors({
origin: true,
credential: true,
}),
);
app.post('/counter', (req, res) => {
res.json('Hello World!');
});
app.listen(4000, () => {
console.log('Back Server On');
});
Back
서버에서 서명을 제외한Transaction
을 만들어서front
로 보내주면front
에서MetaMask
로 연결하여MetaMask
에서 서명을 만들어Transaction
을 발생시킬것 입니다.- 배포한
Contract
에 대한 내용이 필요할 것이니front
에서 사용중이던Counter.json
파일을Back
서버에도 그대로 가져와서 작업을 할 것입니다.
app.post(counter)
코드 작성/* server.js */
app.post('/counter', async (req, res) => {
const { from, type } = req.body; // 0xb81cc9fa0ccd058f5a1669f719bf67e4b97bac56
const transaction = await new Txobj().init(from, type);
console.log(transaction);
res.json(transaction);
});
- 위와 같이
req.body
로Front
에서 요청한 내용의body
내용을 가져와Transaction
의from
내용, 즉address
값과count
를+
할것인지-
할것인지에 대한type
을 받아오도록 하겠습니다.
/* txobj.js */
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545')); // Ganache 네트워크 연결
const CounterContract = require('./contracts/Counter.json');
class Txobj {
async init(_from, _type) {
this.nonce = await Txobj.getNonce(_from);
this.from = _from;
this.to = await Txobj.getCA(_from); // Truffle 디렉토리의 Counter.json 파일을 복사해서 가져오고 CA 계정을 구합니다.
this.data = await Txobj.getData(this.to, _type); // 함수를 실행시킬수 있는 bytecode가 들어갑니다.
return this;
}
//nonce 구해오기
static getNonce = async (_from) => {
return await web3.eth.getTransactionCount(_from);
};
// CA 계정 구해오기
static getCA = async (_from) => {
const networkId = await web3.eth.net.getId();
return CounterContract.networks[networkId].address;
};
// Data 구해오기
static getData = async (_ca, _type) => {
const abi = CounterContract.abi;
const deployed = new web3.eth.Contract(abi, _ca);
switch (_type) {
case 'increment':
return await deployed.methods.increment().encodeABI(); // increment 메서드 자체를 코드를 bytecode로 변환합니다.
case 'decrement':
return await deployed.methods.decrement().encodeABI();
}
};
/*
{
"nonce": 16,
"from": "0xb81cc9fa0ccd058f5a1669f719bf67e4b97bac56",
"to": "0x1a305591E65a493A042Fa445cE44F237DdAC2550",
"data": "0xd09de08a"
}
*/
}
module.exports = { Txobj };
Transaction
이란 객체이니class Txobj
를 생성하여 인자값으로req.body
로 받아온from
과type
을 넣고new
키워드로 새로운Txobj
를 생성하려고 하면 자동으로nonce
,from
,to
,data
를 가진 인스턴스를 생성하도록 해주었습니다.nonce
는web3.eth
의getTransactionCount
메서드를 사용하여 해당 계정으로 몇번의Transaction
이 발동되었는지에 대한nonce
값을 가져왔습니다.from
은 말그대로 인자값으로 받은address
값을 넣어줍니다.to
에는Contract Address
즉Contract
에 대한 주소 값이 들어가는데 먼저networkId
를web3.eth
의getId
메서드를 사용하여 가져온후networkId
를 가지고 배포한Contract
즉Counter.json
파일에서 해당 네트워크의 배포된Contract
의address
값을 찾아와서 넣어주었습니다.- 마지막으로
data
에는 미리 구한CA
값과 인자값으로 받은type
을 사용할 것인데, 배포한Contract
의abi 값을 가져온후 가져온
abi값과 인자값으로 받은
CA를 사용하여 새로운
deployed인스턴스를 생성해주고, 인자값으로 받은
type에 따라
increment일경우
+decrement
일 경우
-를 하도록
deployed인스턴스안에
methods에서
increment메서드 혹은
derement메서드 자체를 컴퓨터가 읽을 수 있도록
bytecode`로 변환하여 넣어주었습니다.
- 이런식으로
class
를 활용하여increment
혹은decrement
를 처리하는Transaction
을 만들어서front
로 보내줄 것입니다.
const increment = async () => {
const response = await axios.post('http://localhost:4000/counter', { from: account, type: 'increment' });
// MetaMask에 Transaction send
await web3.eth.sendTransaction(response.data);
};
const decrement = async () => {
const response = await axios.post('http://localhost:4000/counter', { from: account, type: 'decrement' });
// MetaMask에 Transaction send
await web3.eth.sendTransaction(response.data);
};
- 마지막으로
Front
의increment
,decrement
함수를 수정하여 모두 같은Back
서버의API
로 요청을 보내고type
값만 다르게 주어Back
서버에서increment
혹은decrement
Contract
내용이 포함된Transaction
을 받고 해당Transaction
을 연결된MetaMask
에게 보내 서명을 추가한 뒤 해당Transaction
을 블록에 넣어Smart contract
를 실행할 것입니다.
Counter
의+
혹은-
버튼을 누를때마다Smart Contract
를 실행하여 상태변수를 바꿔줄것이니 가스비를 지불하여Transaction
이txpool
에 담기고 블록이mining
되면transaction
의contract
가 실행되며 상태변수를 바꿔줍니다.