본문 바로가기
프로그래밍/solidity

[220714] 스마트 컨트랙트 구현하기 - 3) 이벤트 (프런트 / 백)

by 한코코 2022. 7. 17.

[ 프런트 ]

매번 새로 생기는 트러플 네트워크 id 확인

$ truffle migration --reset

가끔 트러플이 꺼져서 재배포를 할경우 간혹 이렇게 networks에 네트워크 id가 두개가 생겨있을 수 있다.

트러플을 새로 켤때 네트워크 id가 새로 생성되어서 그런거니까 고유 id값을 확인해서 아닌걸 지우면 된다.

$ truffle console
$ web3.eth.net.getId()

 

 

 

CA 데이터가 자동갱신 되도록 만들기

저번 블로그 글에서 CA값을 json파일에서 찾아서 직접 붙여넣었다.

const Deployed = new web3.eth.Contract(
  CounterContract.abi,
  "0xE538196bCb40F7cc12E3e041D24A9b04FA3A16A8"
);

하지만 트러플을 끄고켤때마다 네트워크 id만 새로 생성되는게 아니라, 그 안의 값들도 모두 바뀐다.

컨트랙트에 접근하려면 ABI와 CA값이 필요한데, CA가 변할때마다 모든 파일을 찾아다니면서 값을 새로 바꾸는건 한계가 있다.

네트워크 id만 알면 그 안에 있는 속성값에 접근할 수 있는 것을 이용해 자동갱신하도록 코드를 짜주자.

const networkId = await web3.eth.net.getId()
const ca = CounterContract.networks[networkId].address
const abi = CounterContract.abi

const Deployed = new web3.eth.Contract(abi,ca)

 

 

 

로그를 남기는 이벤트 등록하기

로그를 찍을건데 이름은 Counter고, 데이터는 uint256에 대한 내용으로 찍는다고 선언하는 이벤트.

// Counter.sol
event Count(uint256 count);

 

어느 시점에 이벤트를 날릴지 작성하기

어느 시점에 데이터를 이더리움 클라이언트에 로그를 찍을지 결정해주는 코드다.

increment 메서드가 실행하면 로그를 찍어줄건데, 현재 내 상태변수에 담고있는 _count를 찍어줄거다.

uint256 private _count;

function increment() public {
  _count += 1;
  emit Count(_count);
}

 

 

어디에 있는 로그를 날릴지 작성하기

CA로 컨트랙트에 접근해서 해당 컨트랙트에 작성해놓은 이벤트 로그메세지를 날리겠다는 코드.

  • subscribe : 정해진 이벤트( "logs" )를 등록함, 무엇에 대한( address : ca ) 이벤트 
  • on : ( 솔리디티 코드에서 emit이 실행될때마다 = )이벤트가 실행될때마다 콜백을 실행하고 무언가( "data" )를 받아옴.
const ca = CounterContract.networks[networkId].address;

web3.eth
.subscribe("logs", { address: ca }) //
.on("data", (log) => {
  console.log(log);
});

 

 

decodeLog

decodeLog는 두가지 인자값을 받아서 객체를 반환한다.

( 디코딩 : 바이트를 문자열로 변환 / 인코딩 : 문자열을 바이트로 변환 )

  1. [{ type: "받을 타입", 변수:데이터 }] : 16진수로 건네받은 데이터를 어떤 타입으로 받아서 파싱할건지
  2. 파싱할 내용 ( 데이터 원본값은 유지하되 형태는 임의로 바꾸는것 )
const params = [{ type: "uint256", name: "_vanilla" }];
const value = web3.eth.abi.decodeLog(params, log.data); // 결과 = obj
setCount(value.count)
console.log(value);

객체를 반환하는 이유

Counter 컨트랙트를 만들 솔리디티 파일에 이벤트 Counter에서 인자값을 하나 가지고 있다.

이게 무슨 소리냐면 이벤트 하나에 데이터가 한개 연결되어있다는 소리다.

( 데이터를 여러개 넣을 수 있지만 3개 이하로 넣는게 좋다고 한다. )

event Counter(uint256 count)

결과값

배열의 첫번째 값은 current 값

배열의 두번째 값은 배열의 총 길이

배열의 세번째 값은 위에서 넣어줬던 name:_count

누군가 증가(increment)버튼을 누르면 화면은 메세지를 받게 된다.

이걸로 메세지를 받을 수 있기 때문에 각 함수 안에서 현재 값을 받던 current()를 삭제해도 된다.

const increase = async () => {
    const result = await deployed.methods.increment().send({
      from: account,
    });
    // if (!result) return;
	// const current = await deployed.methods.current().call();
	// setCount(current);
  };

 

로그 확인하기

트랜잭션 해시값으로 getTransactionReceipt를 사용해서 조회하면 logs를 알 수 있다.

 

web3.eth.getTransactionReceipt('트랜잭션 해시값')

 

 


[ 백 ]

백에서 프론트에게 보내는 객체의 속성들

web3.eth.getTransactionReceipt('트랜잭션 해시값')

백에서 이런 객체를 만들어주고 프런트에 전달( axiois )해주면, 프런트는 여기에서 서명만! 남겨놓은채로 메타마스크에게 전달해준다.

프런트에서도 할 수 있지만 백에서 하는 이유는, 양이 점점 많아지면 번잡스럽기 때문.

 

 

CA값으로 잔액 확인하기

트랜잭션 해시값을 사용하면 CA값을 확인할 수 있다는 것을 알았다.

정말로 이 CA 값이 받는 사람의 CA 계정인지, 정말 사용할 수 있는 값인지 확인하자.

CA와 getBalance를 사용하면 잔액을 확인할 수 있다.

// web3.eth.getBalance('CA값')
web3.eth.getBalance('0xC5a75EE91130087862cc674448942EAf1cf38373')
>> 0

 

 

 

백에서 사용할 server.js 코드

// back/server.js
const express = require("express");
const app = express();
const cors = require("cors");
const router = require("./routes/index");
const Web3 = require('web3')
const web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"))

const corsOption = {
  origin: true,
  credentials: true,
};

app.use(cors(corsOption));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static("public"));
app.use(router);

app.listen(4001);

 

 

Eth 네트워크에게 메서드 실행 요청 메세지 보내기

  let txObject = {
    nonce:, // 트랜잭션 총 횟수
    from:, // 해당 트랜잭션을 발생시긴 사람의 address.account
    to:,
    data:, // 스마트 컨트랙트 함수에 대한 내용을 적음
    gasLimit:, // 가스 최대용량, 따로 설정 안하면 디폴트값이 됨
    gasPrice: // 수수료, 따로 설정 안하면 디폴트값이 됨
  }

data에 스마트 컨트랙트 함수에 대한 내용을 적게되는데,

솔리디티 코드에 있는 increment 메서드를 실행하고싶다고 여기에 적으면 안된다.

increment()는 배포한 ABI에 담겨셔 이더리움 네트워크에 배포된 상태기 때문이다.

여기엔 함수를 실행시킬 수 있는 메세지를 넣어야한다.

 

그 메세지를 보내기 위해서 바이트코드로 바꿔주는 encoded를 사용해야하고, 문자열은 함수니까 ABI를 사용해야한다.

따라서 increment 메서드를 바이트코드 메세지로 바꿔주는 코드는 다음과 같이 작성할 수 있다.

increment().encodedABI()

 

하지만 이 코드를 사용하려면 increment()를 사용하는게 전제조건이 되어야한다.

이더리움 네트워크에 있는 increment()에게 접근하려면 다음 코드를 작성하면 된다.

const deployed = new web3.eth.Contract(abi, ca)
const data = deployed.methods.increment().encodeABI()

 

총 정리하면 다음과 같은 코드가 완성된다.

const Web3 = require('web3')
const web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545")) // ganache
const CounterContract = require('../truffle/build/contracts/Counter.json')

app.post("/api/increment",async (req,res)=>{
  const {from} = req.body

  const nonce = await web3.eth.getTransactionCount(from)
  const networkId = await web3.eth.net.getId()
  const to = CounterContract.networks[networkId].address; //CA
  const abi = CounterContract.abi

  // abi
  // 이걸 쓰려면 increment함수를 호출할 수 있어야함 -> new web3.eth.Contract
  const deployed = new web3.eth.Contract(abi, to)

  // increment().encodeABI() 문자열을 byte코드로 바꿔주는 encodeABI()
  const data = deployed.methods.increment().encodeABI()

  // 트랜잭션 형태 만들기
  let txObject = {
    from, // 해당 트랜잭션을 발생시긴 사람의 address.account
    nonce, // 트랜잭션 총 횟수
    to, // 컨트랙트 어드레스
    data, // 스마트 컨트랙트 함수를 실행시켜달라는 메세지
  }

  res.json(txObject)
})

 

이 상태에서 postman으로 post 요청을 보내도 트랜잭션은 일어나지 않는다.

하지만 data에 16진수가 생성되어 있는것을 볼 수 있다.

이게 increment 메서드를 호출시킬 수 있는 메세지다!

이걸 메타마스크에 전달해주면, 메타마스크가 나머지 부분을 채워주고 서명을 네트워크에 전달하면서 트랜잭션이 생성되게 된다.

 

 

 

 

 


[ 프런트 ]

 api 요청하기 위해 코드 고치기

const increase = async () => {
    const response = await axios.post("http://localhost:3001/api/increment", {
      from: account,
    });

    console.log(response.data)
    await web3.eth.sendTransaction(response.data) // 메타마스크에 던져주자!
    
    // 프런트에서 만들어줘서 넘겨주는 코드는 이제 필요없음
    // const result = await deployed.methods.increment().send({
    //   from: account,
    // });

    // api 요청을 해서 json을 받아와서 네트워크에 넘겨줄거임
};

 

증가 버튼을 눌러서 메타마스크가 서명처리까지 다 끝내고 트랜잭션을 생성한 후,

트러플에서 이 코드를 실행하면 각 함수별 메세지를 볼 수 있고, 객체 안에 input이 존재하는걸 알 수 있다.

Counter.deployed()

즉, input이 있다면 스마트 컨트랙트에 실행한 메서드가 있다는 소리고, to는 CA가 된다는 소리임.

( CA가 있으면 배포한 트랜잭션이고, 없으면 배포하지 않은 트랜잭션임 )

 

 

 

잊을까봐 다시 적는 트랜잭션 종류

  1. 돈을 보내는 트랜잭션 -> 금액
  2. 스마트 컨트랙트 배포한 트랜잭션 / 안 한 트랜잭션 -> CA
  3. 스마트 컨트랙트 실행한 트랜잭션 / 안 한 트랜잭션 -> input

댓글