일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Vue.js
- ethers type
- ethers typescript
- 프록시배포구조
- ethers websocket
- 러스트기초
- 러스트 기초 학습
- 스마트 컨트렉트 함수이름 중복
- 스마트컨트렉트프록시
- 스마트컨트렉트 예약어 함수이름 중복
- 깃허브명령어
- nestjs 튜토리얼
- ethers
- 컨트렉트 배포 자동화
- git rebase
- SBT표준
- ambiguous function description
- vue기초
- 스마트컨트렉트테스트
- 스마트컨트렉트 함수이름 중복 호출
- ethers v6
- 러스트 기초
- 머신러닝기초
- rust 기초
- chainlink 설명
- nest.js설명
- multicall
- 컨트렉트 동일한 함수이름 호출
- 체인의정석
- Vue
- Today
- Total
체인의정석
ethers & websocket & http로 이벤트 구독 및 크로스체인 트랜잭션 실행, 트랜잭션 정보값 뽑아내기 본문
ethers & websocket & http로 이벤트 구독 및 크로스체인 트랜잭션 실행, 트랜잭션 정보값 뽑아내기
체인의정석 2024. 7. 18. 10:18크로스체인 트랜잭션 실행 로직을 만들 때 만든 코드 중 다시 사용할 만한 코드만 뽑아서 기록해둔다. 해당 로직은 Ethers v6 버전으로 진행하였다.
A. Ethers & WebSocket 사용하여 이벤트 구독하기
A-1. ping, pong 을 통해 커넥션 살리기
이벤트 구독이 일시적으로 되더라도 계속해서 ws 구독을 유지시키려면 다음과 같이 체크 로직을 만들어서 돌려주어야 한다.
const { ethers } = require("ethers");
const ResilientWebsocket = async (url) => {
const EXPECTED_PONG_BACK = 15000;
const KEEP_ALIVE_CHECK_INTERVAL = 30 * 1000; //7500;
const debug = (message) => {
console.debug(new Date().toISOString(), message);
};
let terminate = false;
let pingTimeout = null;
let keepAliveInterval = null;
let wsp;
const startConnection = (url) => {
wsp = new ethers.WebSocketProvider(url);
setInterval(() => {
debug("Checking if the connection is alive, sending a ping");
wsp.websocket.ping();
pingTimeout = setTimeout(() => {
console.error("pingTimeout");
if (wsp) wsp.websocket.terminate();
handleWebSocketConnectionDead();
}, EXPECTED_PONG_BACK);
}, KEEP_ALIVE_CHECK_INTERVAL);
};
function handleWebSocketConnectionDead() {
terminate = true;
if (keepAliveInterval) clearInterval(keepAliveInterval);
if (pingTimeout) clearTimeout(pingTimeout);
throw new Error("WebSocket connection closed with code ", e);
};
startConnection(url);
wsp.websocket.on('pong', () => {
debug("Received pong, so connection is alive, clearing the timeout");
if (pingTimeout) clearTimeout(pingTimeout);
});
console.log("ws checking has started!")
return wsp;
};
module.exports = {
ResilientWebsocket
}
B. 이벤트 구독하기
먼저 ABI, 주소, provider를 받아와서 다음과 같이 이벤트를 구독할 수 있다. 이벤트가 발생되면 handleSendMessageA 함수가 실행된다.
provider = new ethers.JsonRpcProvider(ENDPOINT);
ContractEX = new ethers.Contract(Address, ABI, provider);
ContractEX.on("Eventname", async (...args) => {
console.log(`Listening for Eventname events from A chain`);
await handleSendMessageA(...args);
});
handleSendMessageA 함수는 다음과 같다. 만약 해당 이벤트를 통해서 다른 트랜잭션을 발생시키고 싶다면 아래와 같이 실해앟면 된다. 아래 예시는 A체인에 이벤트가 발생할 시 B체인에 이벤트를 발생시키는 예시이다. websocket으로는 트랜잭션을 쏠 수 없기에 트랜잭션을 쏘기 위하여 privateKey정보가 담긴 wallet 객체를 따로 만들어서 해당 부분을 구현하였다.
const handleSendMessageA = async (caller, uri, params, deadline, event) => {
try {
providerA = new ethers.JsonRpcProvider(providerUrl_A);
walletA = new ethers.Wallet(privateKeyA, providerA);
ContractA = new ethers.Contract(ContractAAddress, ContractAABI, walletA);
providerB = new ethers.JsonRpcProvider(providerUrl_B);
walletB = new ethers.Wallet(privateKeyB, providerB);
erc721LaneB = new ethers.Contract(ContractBAddress, ContractBABI, walletB);
const BTx = await BTx.exTX(input1, input2);
await BTx.wait();
} catch (error) {
console.log("handleSendMessageA error >>", error);
}
};
C. 이벤트 구독하면서 TXhash 및 blockTimstamp, data 뽑아내기
만약 여기서 트랜잭션 해시를 뽑아내고 데이터도 뽑아내고 싶다면 아래와 같이 진행하면 된다.
위의 응답값에서 event.log.transactionHash를 가져오면 txHash를 도출할 수 있다.
const txHash = event.log.transactionHash;
해당 txhash로 부터 블록번호, 가스사용량, blockTimstamp는 다음 함수를 통해서 뽑아낼 수 있다.
const getTxDataFromHash = async (provider, txhash) => {
const txReceipt = await provider.getTransactionReceipt(txhash);
const res = {};
res.blockNumber = Number(txReceipt.blockNumber);
res.gasUsed = Number(txReceipt.gasUsed);
const blockTimestamp = await provider.getBlock(Number(txReceipt.blockNumber));
res.blockTimestamp = blockTimestamp.timestamp;
return res;
}
module.exports = {
getTxDataFromHash
}
* ethers v6의 경우 provider는 다음과 같이 넣어주면 된다.
provider = new ethers.JsonRpcProvider(ENDPOINT);
위의 timestamp 가져와서 DB에 시간 기록할 때는 아래 함수를 사용할 것.
function formatDateToMySQLDatetime(timestamp) {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
module.exports = {formatDateToMySQLDatetime};
D. endpoint가 불안정할 경우
만약 endpoint를 무료 버전으로 사용할 경우 이벤트 구독이 잘 작동하다가도 끊길 수가 있다.
이경우 유로 endpoint를 구독하거나 직접 노드를 구축해야하는데 임시적으로 여러 enpoint를 받아와서 재시작하는 로직을 넣어줄 수 있다. 이때 이벤트 구독을 해주는 websocket을 제대로 제거하지 않으면 중복적으로 이벤트를 구독할 수 있기 때문에 정확히 객체를 지정해서 삭제해주고 주기적으로 종료시켜주면 docker-compose가 다시 띄워주는 구조로 만들어서 해결이 가능하다.
const shutdown = () => {
console.log('Shutting down the process after 5 minutes');
delete 이벤트구독 함수(변수);
delete ResilientWebsocket;
process.exit(0); //일반적인 경로로 종료되었음을 표시
};
setTimeout(shutdown, 300 * 1000); // Schedule shutdown after 5 minutes (300,000 milliseconds)
가장 좋은것은 잘 작동하는 enpoint를 여러개 두고 문제가 생기면 갈아서 다시 삭제하고 시작하는 로직이지만 무료 enpoint는 내부적으로 에러 로그만 찍고 끝내버리는 경우가 많아서 위의 방법이 임시적으로는 유효하다.
'블록체인 > Ethers & web3' 카테고리의 다른 글
hardhat gas reporter 외부 설치 없이 사용하기 (0) | 2024.08.02 |
---|---|
ethers v6 이벤트 구독하고 유지시키는 방법 정리 (0) | 2024.07.04 |
ethers v5 -> v6 문법 (0) | 2024.04.15 |
ethers 6버전 이상에서 ethers.utils가 인식이 안되는 에러 (0) | 2023.12.24 |
hardhat, ethers 의 ProviderError: HttpProviderError (0) | 2023.07.21 |