박주니 개발 정리

노드 모니터링) 블록체인 owner address 확인 본문

블록체인

노드 모니터링) 블록체인 owner address 확인

박주니 2023. 3. 22. 17:04
728x90
반응형

사용 이유) nft 마켓을 운영하게 될 때 1차 판매 관리자가 사용자에게 판매해서 owner address가 사용자로 지정이 되었고 그 사용자가 nft 자체 마켓이 아닌곳 예를 들어서 opensea에서 회원이 아닌 다른 사용자에게 nft 판매했을때에는 기록을 따로 관리하거나 삭제를 해야하기때문에 그것을 체크하고 관리하기 위해서 구현하게 되었습니다. 

 

초기 설정) node, pm2 

  • pm2 설정하는 방법은 node-pm2 설정으로 별도 정리해서 올렸습니다. 참고하시면 됩니다. 

 

1. web3를 설치하고 web3를 설정합니다. 

추가 설명) endpoint 설정은 infura 적용을 해봤지만 안돼서 quicknode에 받은 url 설정했습니다.

const Web3 = require("web3");
const web3 = new Web3(
// quicknode endpoint
  "https://sleek-muddy-replica.matic.discover.quiknode.pro/.../"
);

2. 스마트 컨트렉트 주소, 기준 wallet address(보통은 발행자 주소)를 미리 설정합니다. 

추가설명) filter을 설정하기 위한 준비 단계

const contractAddress = "0x6c9912......de4";
const fromAddress = "0xb.......AAD";

상세 설명) 

contractAddress) 먼저 이 내용을 이해하기 위해서는 블록체인/performanceNFT 적용 부분을 확인해주시길 바랍니다. 예를들어서 performanceNFT를 구매한 사용자를 확인하는 거라면 contractAddress는 performanceNFT address 입니다. 

fromAddress) 저는 관리자 기준으로 처음에 발행시에는 null이기때문0x0000000000000000000000000000000000000000

polygonscan에서 확인이 되었을 것입니다. 그리고 발행된 nft를 사용자가 구매시 fromAddress는 발행자 주소로 변경되었고 toAddress는 구매자 즉 사용자 주소로 나올 것입니다. 

 

3. filter을 셋팅합니다. (erc20 transfer event signature 공용 주소 포함)

const filter = {
  address: contractAddress,
  topics: [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // ERC-20 Transfer event signature
    "0x000000000000000000000000" + fromAddress.slice(2), // 발행자 주소를 포함하는 토픽
  ],
};

추가 설명) topics를 이해하기 위해서는 트랜잭션이 정상적으로 진행되었다고 가정했을 때 polygonscan에 들어가서 logs를 확인하시면 topics 구성을 확인하실 수 있습니다.

구성을 확인해보시면 fromAddress.slice(2)를 하게되면 0x를 제외한 나머지 부분을 붙이는 것이기 때문에 topics 구성 형식과 비슷해집니다. 

원리 설명) 

erc20 transfer event signature) 거래가 일어났을 때는 erc20 transfer을 사용하기 때문에 topics[0]을 보면 동일한 signature값을 확인하실 수 있습니다. 

발행자 주소를 포함한 토픽) 현재 스마트컨트렉트는 전체 공유하는 주소이기 때문에 발행자 주소를 따로 filter하지 않으면 얻고자 하는 데이터 외에 수많은 정보들을 가지고 오기 때문에 해당 거래를 fitler하기 위해서는 발행자 주소나 아니면 지정하고자 하는 address를 filter에 넣어야합니다. 

 

4. blockNumber을 지정해서 해당 log를 가지고 올 함수에 매개변수로 넘깁니다. 

  const targetBlock = 40...8; // block number db에서 반복문으로 가져올 부분
  const fromBlock = targetBlock;
  const toBlock = targetBlock;

  const logs = await getLogsInRange(fromBlock, toBlock, filter);

추가 설명) 먼저 blockNumber을 설정하지 않으면 filter에 적용된 모든 데이터를 가지고 오게 됩니다. 물론 확인하는 용도로는 괜찮을 수 있지만 보통은 transfer이 된 blockNumber을 recordNFT 또는 db에서 관리하다가 해당 정보를 가지고와서 반복문으로 check를 진행할 수 있기 때문에 blockNumber을 불러와서 확인하는 개념으로 진행했습니다.

polygonscan에서 transaction details를 확인하시면 block이 보이는 것을 확인할 수 있을것입니다. 그 block number가 가져오고자하는 대상이기 때문에 대입하시면 됩니다. 

 

5. getLogsInRange 함수를 셋팅합니다.

async function getLogsInRange(fromBlock, toBlock, filter) {
  const logs = [];
  const maxBlockRange = 10000;

  for (
    let currentFromBlock = fromBlock;
    currentFromBlock <= toBlock;
    currentFromBlock += maxBlockRange + 1
  ) {
    const currentToBlock = Math.min(currentFromBlock + maxBlockRange, toBlock);
    const currentFilter = {
      ...filter,
      fromBlock: currentFromBlock,
      toBlock: currentToBlock,
    };
    const currentLogs = await web3.eth.getPastLogs(currentFilter);
    logs.push(...currentLogs);
  }

  return logs;
}

핵심) await web3.eth.getPastLogs(currentFilter)

filter에 fromBlock과 toBlock도 같이 셋팅해서 값을 보내주면 해당 영역과 filter에 적용된 로그를 가지고 오게 됩니다. 현재 fromBlock과 toBlock이 동일한 값을 전달하는 것을 볼 수 있습니다. 

 

6. 해당되는 log의 값을 지정한 fromAddress와 동일한지 체크하고 toAddress를 가지고 옵니다. 

  logs.forEach((log) => {
    const logFrom = "0x" + log.topics[1].slice(26);
    if (logFrom.toLowerCase() === fromAddress.toLowerCase()) {
      const logTo = "0x" + log.topics[2].slice(26);
      console.log("To:", logTo);
    }
  });

참고 설명) 이 코드를 이해하기 위해서는 polygonscan에 logs topics 구성을 이해해야합니다.

topics[0]) ERC-20 Transfer event signature

topics[1]) fromAddress

topics[2]) toAddress

 

  1. fromAddress 체크 
  2. 해당 영역에서 toAddress (ownerAddress)

추가 옵션)  시간 설정 

시간 예외처리 없이 pm2 연결된 상태로 진행하면 계속 체크하고 값이 나오는 것을 확인할 수 있습니다. 

그렇게 되다보다보면 무거워질 수 있기 때문에 저는 지금 노드 모니터링을 12시간 간격으로 체크하는 것으로 수정해봤습니다. 

 

const MS_IN_12_HOURS = 12 * 60 * 60 * 1000;
setInterval(main, MS_IN_12_HOURS);

분석)

1000은 1초를 의미한다면 현재 적혀진 숫자를 곱하면 12시간을 의미하는 것을 알 수 있을 것입니다. 

setInterval은 시간을 설정해서 이벤트를 적용할 때 사용합니다. 

 

전체 코드

const Web3 = require("web3");
const web3 = new Web3(
  "https://sleek-muddy-replica.matic.discover.quiknode.pro/...../"
);

const contractAddress = "0x6c99....65de4";
const fromAddress = "0xb2......D";
const MS_IN_12_HOURS = 12 * 60 * 60 * 1000;
const filter = {
  address: contractAddress,
  topics: [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // ERC-20 Transfer event signature
    "0x000000000000000000000000" + fromAddress.slice(2), // 발행자 주소를 포함하는 토픽
  ],
};

async function getLogsInRange(fromBlock, toBlock, filter) {
  const logs = [];
  const maxBlockRange = 10000;

  for (
    let currentFromBlock = fromBlock;
    currentFromBlock <= toBlock;
    currentFromBlock += maxBlockRange + 1
  ) {
    const currentToBlock = Math.min(currentFromBlock + maxBlockRange, toBlock);
    const currentFilter = {
      ...filter,
      fromBlock: currentFromBlock,
      toBlock: currentToBlock,
    };
    const currentLogs = await web3.eth.getPastLogs(currentFilter);
    logs.push(...currentLogs);
  }

  return logs;
}

async function main() {
  const targetBlock = 4....58; // block number db에서 반복문으로 가져올 부분
  const fromBlock = targetBlock;
  const toBlock = targetBlock;

  const logs = await getLogsInRange(fromBlock, toBlock, filter);
  logs.forEach((log) => {
    const logFrom = "0x" + log.topics[1].slice(26);
    if (logFrom.toLowerCase() === fromAddress.toLowerCase()) {
      const logTo = "0x" + log.topics[2].slice(26);
      console.log("To:", logTo);
    }
  });
}

main();

setInterval(main, MS_IN_12_HOURS);

 

728x90
반응형
Comments