블록체인 Block-Chain - 이더리움 Smart Contract 배포 및 실행하기 ( JS )

dev_swan·2022년 7월 13일
0

블록체인

목록 보기
20/36
post-thumbnail
post-custom-banner

keystore를 복호화하여 개인키 구해보기 ( Keythereum )

개발환경 세팅

  • npm init -y
  • npm install keythereum web3
  • keystore란?
    • 암호화된 계정 정보들이 객체로 들어있는 파일들입니다.
    • 이때 사용한 암호화는 양방향 암호화이기 때문에 계정을 생성할 때 입력한 계정의 PassWord를 이용하여 복호화하여 계정주소를 다시 Private Key로 변환할 수 있습니다.
const keythereum = require('keythereum');
const path = require('path');
const account = '0x3c9b17a493558da518ff12b81df75e673ec59ab6';
const dir = path.join(__dirname); // keystore 상위 디렉토리 경로
// keystore 디렉토리에서 account와 같은 데이터가 있는 객체를 리턴
const keyObject = keythereum.importFromFile(account, dir);

console.log(keyObject);

/*
{
  address: '3c9b17a493558da518ff12b81df75e673ec59ab6',
  crypto: {
    cipher: 'aes-128-ctr',
    ciphertext: '5189dd114bd013db5e7b05c1b6b9afd310f5af351a0265482aa2f42e24de895a',
    cipherparams: { iv: 'c93d8eaf1ee6fbb80a1198d72194c594' },
    kdf: 'scrypt',
    kdfparams: {
      dklen: 32,
      n: 262144,
      p: 1,
      r: 8,
      salt: 'bd8c5eaf7f35d3f1c638140355271252257c9f4cf587b4884b97da7c4a75af48'
    },
    mac: 'e0432ca39c7f17603f28e233b7ecc0582bc6f98bc13d0b59339c106c84d3655b'
  },
  id: '47029531-6c7d-4e51-b5d0-c92567054e70',
  version: 3
}
*/

// 받아온 객체를 가지고 계정을 생성할 때 사용한 Password로 복호화를 하여 개인키를 구해옵니다.
const privateKey = keythereum.recover('1234', keyObject).toString('hex');

console.log(privateKey);
// 78cd9b04766b3902c182e8f8663f2f93e0624753a7eb7a2020af72b80576a46f

솔리디티 코드 작성

pragma solidity ^0.8.15;

contract HelloWorld {
    // 상태변수 선언
    string public value;

    // 생성자
    constructor(){
        value = "Hello World!";
    }

    // 상태변수를 가져오는것 함수 getter
    function getValue() public view returns (string memory) {
        return value;
    }

    // 상태변수를 바꿔주는 함수 setter
    function setValue(string memory _value) public {
        value = _value;
    }
}
  • 상태변수를 위처럼 public으로 선언하였을 경우 상태 변수값을 가져오는 getter 함수를 알아서 만들어줍니다.

솔리디티 코드 컴파일링 및 내보내기

/* Complie.js */\
class Contract{
  static compile(_filename) {
      const constractPath = path.join(__dirname, '../contracts', _filename);

      const data = JSON.stringify({
          language: 'Solidity', // 어떤 언어로 컴파일링을 진행할 것인가.
          sources: {
              [_filename]: {
                  content: fs.readFileSync(constractPath, 'utf8'),
              },
          },
          settings: {
              outputSelection: {
                  '*': {
                      '*': ['*'],
                  },
              },
          },
      });

      const compiled = JSON.parse(solc.compile(data));

      this.writeOutput(compiled);
  }

  static writeOutput(_compiled) {
      for (const constractFileName in _compiled.contracts) {
          const [contractName] = constractFileName.split('.');
          const contract = _compiled.contracts[constractFileName][contractName];

          const abi = contract.abi;
          const bytecode = contract.evm.bytecode.object;

          console.log(abi)
          console.log(bytecode)

          const obj = {
              abi,
              bytecode,
          };

          const buildPath = path.join(__dirname, '../build', `${contractName}.json`);
          fs.outputJSONSync(buildPath, obj);
      }
  }
}

module.exports = { Contract };
  • Contract 클래스로 Solidity Code로 작성한 파일명을 인자값으로 받고 해당 파일명을 가지고
    해당 파일의 Path를 ( 현재 실행중인 폴더 경로(--dirname) + /contracts + 인자값으로 받은 파일명) 으로 구하였습니다.
  • 이제 solc.compile() 메서드로 컴파일링을 할 것인데 인자값으로 받는 data 객체로 안에는
    컴파일링을 진행할 파일의 사용 언어 (language), 컴파일링할 파일의 내용 (source), 설정값 (settings)이 들어갑니다.
  • 아래는 compile한 내용을 JSON.parseJSON형식으로 만든값입니다. 아래와 같이 compile이 완료되면 컴파일한 내용을 writeOutput 메서드의 인자값으로 넣고 해당 메서드를 실행합니다.
  • 이제 writeOutput 메서드를 보면 for in 문을 사용하여 complied.contracts 객체의 key값('HelloWorld.sol')을 가져온후 split을 사용하여 .을 기준으로 첫번째 index값인 ( HelloWorld )을 가져왔습니다.
  • 위에서 가져온 'HelloWorld.sol'과 HelloWorld를 가지고 _compiled.contracts 객체 안에 있는 abi 파일과 bytecode 값을 가져올 것입니다. _compiled.contracts의 내용은 아래와 같습니다.
  • 이제 각각 구해온 abi값과 bytecode값을 buildPath 경로의 HelloWorld.json 파일로 만들것이고 그 안에는 구해온 abibytecode값이 들어갈 것 입니다.

스마트 컨트랙트 배포 및 실행

/* Client.js */
const Web3 = require('web3');

let instance;
class Client {
	constructor(_url) {
		if (instance) return instance;
		this.web3 = new Web3(_url);
		instance = this;
	}
}

module.exports = { Client };
  • 블록체인 네트워크에 배포를 하기위해 url을 인자값으로 받아 web3 라이브러리를 사용하여 새로운 web3 인스턴스를 생성하는 Class를 만들어주었습니다.
  • 위의 코드를 보면 이 Class로 인스턴스를 생성할 경우 오직 1개의 Class만 생성되는것을 확인할 수 있는데, 생성자 메서드가 여러번 호출되어도 생성되는 객체는 최초의 하나이고 이후에는 이미 생성된 인스턴스를 return 해줍니다.
  • 이러한 패턴을 싱글톤(Singleton) 패턴이라고 하는데 이런식으로 구현하는 이유는 싱글톤 패턴으로 생성한 인스턴스는 전역으로 사용되는 인스턴스이기 때문에 접근성이 용이하고, 메모리 적인 측면에서도 불필요한 인스턴스를 생성하지 않아 훨씬 효율적이라고 할 수 있습니다.
/* index.js */
const { Contract } = require('./controllers/compile.js');
const { Client } = require('./controllers/client.js');

const [abi, bytecode] = Contract.compile('HelloWorld.sol');

const client = new Client('ws://127.0.0.1:9000');

// txObj에 bytecode를 넣어줍니다.
const txObject = {
	data: bytecode,
};

// abi 내용이 포함된 Contract에 대한 instance가 생성됩니다.
// bytecode들을 incoding하여 읽을수 있게 해주는 파일 = abi
const contract = new client.web3.eth.Contract(abi);

async function init() {
	// Contract 배포 => 트랜잭션이 발생했다는것 => Mining을 해야한다.
	const instance = await contract.deploy(txObject).send({ from: '71083ec003149f2f1bfe62ed8fd9fbe8c6902bf9' });

	// miner.start(4)

	const ca = instance.options.address;
	const deployed = new client.web3.eth.Contract(abi, ca);

	const data = await deployed.methods.value().call();
	console.log('before', data);

	// Contract를 실행하면서 수수료를 지불하며 Txpool에 담기고 블록에 담겨야 contract가 실행되면서 value값이 바뀐다.
	await deployed.methods.setValue('Hello Contract!').send({ from: '71083ec003149f2f1bfe62ed8fd9fbe8c6902bf9' });

	// miner.start(4)

	const changed = await deployed.methods.value().call();
	console.log('after', changed);
}

init();
  • 이제 솔리디티 코드작성, 컴파일링 및 블록체인 네트워크에 접근하는 Class들은 모두 만들었으니 실제로 컴파일링된 스마트 컨트랙트를 배포하고 실행해보도록 하겠습니다.
  • Contract.compile('HelloWorld.sol') 메서드를 사용하여 해당 솔리디티 코드를 컴파일링한 abibytecode 값을 return 받고 abinew client.web3.eth.Contract(abi)의 인자값으로 넣어 abi 내용을 포함하고 있는 contract 인스턴스를 생성해 주었습니다.
  • 이제 init 함수를 실행하여 contractdeploy메서드의 인자값에 배포할 bytecodetxObj를 넣고 .send() 메서드를 사용하여 스마트 컨트랙트를 배포할 지갑 주소를 넣어주고 실행하면 bytecodedata로 갖는 Transaction 객체가 생성되어 Transaction Pool에 담기게 되고 아직 mining이 된 상태가 아니니 블록체인 네트워크에는 배포되지 않은 상태입니다.
  • 이후 mining을 하면 Transaction Pool에 있던 Transaction이 블록에 담겨 블록체인 네트워크에 배포됩니다.
  • 배포된 스마트 컨트랙트를 실행하기 위해 해당 컨트랙트의 CA값을 가져온후 abi 내용과 CA 값을 가진 deployed 인스턴스를 생성하였습니다.
  • deployed 객체의 methods안에 있는 value() 메서드를 call하여 상태변수 값을 가져올 수 있습니다.
  • setValue() setter 함수로 상태변수의 값을 Hello Contract!로 바꾸려면 Transaction이 발생되니 해당 Transaction의 가스 수수료를 지불할 지갑 주소를 .send 메서드에 인자값으로 적어줍니다. 이제 상태변수를 바꾸려는 TransactionTransaction Pool에 담겼으니 다시 mining을 해준후 value().call() getter 함수로 value값을 찍어보면 상태변수가 바뀌어 있는것을 확인할 수 있습니다.

Truffle

  • 솔리디티 스마트 컨트랙트 개발을 할때 많이 사용하는 프레임워크 입니다. ( web3 라이브러리 기반 )
  • 이 외에 하드햇이라는 프레임워크도 있습니다. ( ethers 라이브러리 기반 )
  • 설치 및 디렉토리 생성
  • npm install -g truffle
  • truffle version ( 설치 및 버전 확인 )
  • npx truffle init

Truffle 프레임워크에서 사용하는 디렉토리와 파일의 역할은 다음과 같습니다.

  • build : 솔리디티 코드를 컴파일한 내용을 담는 디렉토리
  • contracts : 솔리디티 코드를 담는 디렉토리
  • migrations : 배포하는 코드를 contract 별로 나눠서 정리한 디렉토리
  • test : 배포된 Contract를 실행시켜보는 테스트 공간
  • truffle-config.json : 네트워크에 대한 설정값등 Truffle의 환경설정이 들어있는 파일

Truffle 스마트 컨트랙트 배포 순서

  1. HelloWorld.sol 파일 그대로 contracts 디렉토리에 작성합니다.

  2. npx truffle compile 솔리디티 코드를 컴파일링합니다.

  3. build 디렉토리에 HelloWorld.sol 솔리디티 코드가 컴파일된 내용들이 담깁니다.

  4. 2_depoly_HelloWorld 파일 생성합니다. migrations 파일구조는 ([번호]_내용_Contract명) 입니다.

const HelloWorld = artifacts.require('HelloWorld'); // HelloWorld.json 파일 가져오기

module.exports = function (deployer) {
	deployer.deploy(HelloWorld);
};
  1. npx truffle migration - HelloWorld.json파일 가지고 블록체인 네트워크에 Contract 배포하기 -> 여기에서 발생한 가스비는 default로 coinbase 지갑 주소에서 지불합니다.

  2. 아직 txpool에 담겨있으니 miner.start(4)을 해줍니다.

  3. npx truffle console -> 명령줄이 바뀝니다. 이것은 geth console과 같습니다.

  4. HelloWorld => 객체가 나옵니다. Contract에 대한 내용 , 가장 마지막에 배포된 Contract와 기존의 web3를 합친 객체가 나옵니다.

  5. HelloWorld. 탭두번을 누르면 배포에 대한 내용이 나옵니다.

  6. HelloWorld.address => CA값을 리턴합니다. 0xDeaAD4CDd4A013f713e81BB2393e2733a435e4A2

  7. HelloWorld.deployed().then(instance => hello = instance ) 배포된 Contract를 hello라는 변수에 담았다. 여기는 Contract에 대한 내용만 들어있는 객체로 리턴 됩니다.

  8. hello. 탭 두번

  9. hello.value() => Hello World!

  10. hello.setValue('Hello Contract!') => Mining => TxReceipt 값이 나옵니다.

  11. hello.value() => Hello Contract!

post-custom-banner

0개의 댓글