Decentralization

이더리움 기반 간편 투표 시스템 구축하기 Ganache, Solidity, Web3.js로 스마트 컨트랙트 배포 및 웹 애플리케이션 개발

이영훈닷컴 2025. 3. 15. 12:15
728x90

이더리움 블록체인 기반 투표 시스템 구축하기

1. 개발 환경 설정

라이브 블록체인에서 직접 개발하는 대신, Ganache라는 인메모리 블록체인(블록체인 시뮬레이터)을 사용하여 앱을 개발할 것입니다. 이 튜토리얼의 2부에서는 실제 블록체인과 상호 작용하는 방법을 다룹니다.

Ganache는 자동으로 10개의 테스트 계정을 생성하며, 각 계정에는 100개의 가짜 이더가 미리 로드되어 있습니다. 이를 활용하여 테스트 블록체인을 실행할 수 있습니다.

Ganache 다운로드 및 설치

Ganache는 이더리움 스마트 컨트랙트를 개발 및 테스트하는 데 유용한 개인 블록체인 환경을 제공합니다. 아래는 Ganache를 다운로드하고 설치하는 방법입니다.

  1. Ganache 다운로드

    • Ganache 공식 웹사이트에 접속합니다.
    • 운영 체제(Windows, macOS, Linux)에 맞는 설치 파일을 다운로드합니다.
  2. 설치 과정

    • Windows
      • 다운로드한 .exe 파일을 실행합니다.
      • 설치 마법사의 지시에 따라 Ganache를 설치합니다.
    • macOS
      • 다운로드한 .dmg 파일을 열고, Ganache 아이콘을 Applications 폴더로 드래그하여 설치합니다.
    • Linux
      • 다운로드한 .AppImage 파일에 실행 권한을 부여합니다:
        chmod +x ganache-*.AppImage
      • 터미널에서 다음 명령을 실행하여 Ganache를 시작합니다:
        ./ganache-*.AppImage
  3. Ganache 실행 및 사용

    • 설치가 완료되면 Ganache를 실행합니다.
    • Ganache는 기본적으로 10개의 테스트 계정을 자동 생성하며, 각 계정에 테스트용 이더(ETH)가 미리 로드됩니다.
    • 이를 활용하여 스마트 컨트랙트를 배포하고 테스트할 수 있습니다.

자세한 내용은 Ganache 공식 문서를 참고하세요.

간편 투표 스마트 컨트랙트

우리는 Solidity 프로그래밍 언어를 사용하여 투표 스마트 컨트랙트를 작성할 것입니다. 객체 지향 프로그래밍에 익숙하다면 Solidity 컨트랙트를 배우는 것은 비교적 쉽습니다.

이제 Ganache 환경을 활용하여 스마트 컨트랙트를 작성하고 배포하는 과정을 진행해보겠습니다.

2. 간편 투표 스마트 컨트랙트 작성하기

이제 Solidity 프로그래밍 언어를 사용하여 투표 스마트 컨트랙트를 작성하겠습니다. Solidity는 객체 지향 언어와 유사한 구문을 사용하므로 OOP에 익숙하다면 쉽게 배울 수 있습니다.

우리는 다음과 같은 기능을 갖춘 Voting 컨트랙트를 작성할 것입니다:

  • 후보자 목록을 저장하는 생성자
  • 특정 후보자가 받은 총 투표 수를 반환하는 함수
  • 특정 후보자에게 투표할 수 있는 함수

스마트 컨트랙트 코드 (Voting.sol)

// SPDX-License-Identifier: UNLICENSED
// SPDX 라이선스를 지정하지 않았음을 나타냅니다. (UNLICENSED: 라이선스 없음)

pragma solidity ^0.6.4;
// Solidity 컴파일러 버전을 0.6.4로 설정합니다.

contract Voting {
// Voting 스마트 컨트랙트 정의

  mapping (bytes32 => uint256) public votesReceived;
  // 후보자별 받은 투표 수를 저장하는 매핑 (key: 후보자 이름, value: 투표 수)

  bytes32[] public candidateList;
  // 후보자 목록을 저장하는 배열

  constructor(bytes32[] memory candidateNames) public {
    candidateList = candidateNames;
  }
  // 생성자: 후보자 이름 리스트를 받아서 candidateList에 저장

  function totalVotesFor(bytes32 candidate) view public returns (uint256) {
    require(validCandidate(candidate));
    // 후보자가 유효한지 검증
    return votesReceived[candidate];
    // 해당 후보자의 총 투표 수 반환
  }

  function voteForCandidate(bytes32 candidate) public {
    require(validCandidate(candidate));
    // 후보자가 유효한지 검증
    votesReceived[candidate] += 1;
    // 해당 후보자의 투표 수 증가
  }

  function validCandidate(bytes32 candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
  }
  // 후보자가 candidateList에 존재하는지 확인하여 true/false 반환
}

이제 이 스마트 컨트랙트를 Ganache 블록체인에 배포해 보겠습니다.

Remix 사용 방법

  1. Remix 웹사이트 접속
    Remix IDE에 접속합니다.

  2. 새 파일 생성
    왼쪽 패널에서 contracts 폴더를 클릭하고, 새로운 Solidity 파일을 생성합니다. 예를 들어, Voting.sol이라는 파일을 생성합니다.

  3. 코드 작성
    앞서 작성한 Solidity 코드를 Remix에 복사하고 붙여넣습니다. Remix에서는 Solidity 컴파일러가 내장되어 있으므로, 컴파일 버튼을 클릭하여 코드를 컴파일할 수 있습니다.

  4. 컴파일
    오른쪽 패널에서 Solidity Compiler를 선택하고, 원하는 버전(0.6.4)을 설정한 후 Compile Voting.sol을 클릭하여 컴파일합니다.

  5. 배포
    컴파일이 완료되면 Deploy & Run Transactions 패널을 열고, EnvironmentInjected Web3로 설정하여 MetaMask와 연결할 수 있습니다. Ganache를 사용하는 경우에는 Web3 Provider를 설정하여 로컬 Ganache와 연결할 수 있습니다.

  6. 배포 파라미터 설정
    Deploy 버튼을 클릭하기 전에, 생성자 파라미터로 후보자들의 이름을 전달해야 합니다. 예를 들어, ['Rama', 'Nick', 'Jose']와 같은 배열을 입력합니다.

    예시:

    • candidateNames 파라미터로 배열을 전달합니다. 이를 위해 ["0x52616d6100000000000000000000000000000000000000000000000000000000", "0x4e69636b00000000000000000000000000000000000000000000000000000000", "0x4a6f736500000000000000000000000000000000000000000000000000000000"]와 같이 Hex로 인코딩된 후보자 이름을 입력할 수 있습니다.

    이 Hex 값들은 각각 후보자의 이름을 bytes32 형식으로 변환한 값입니다.

    • Rama: 0x52616d6100000000000000000000000000000000000000000000000000000000
    • Nick: 0x4e69636b00000000000000000000000000000000000000000000000000000000
    • Jose: 0x4a6f736500000000000000000000000000000000000000000000000000000000

    이 값을 Remix에서 제공하는 배포 파라미터에 입력하면 됩니다.

  7. 배포 실행
    배포 버튼을 클릭하여 스마트 컨트랙트를 배포하고, 배포된 컨트랙트 주소를 확인합니다.

2. 스마트 컨트랙트에 대한 디플로이 정보 추가

스마트 컨트랙트가 배포되면, 해당 컨트랙트의 주소를 사용하여 웹 애플리케이션에서 상호작용할 수 있습니다. 아래는 이더리움 블록체인에 배포된 스마트 컨트랙트 주소 예시입니다.

배포된 스마트 컨트랙트의 주소:

0xbf660D72d597Db3DfB58A48fEbC61961B25f015F

위 주소는 Remix에서 스마트 컨트랙트를 배포하고 나면 자동으로 생성됩니다. 이 주소를 웹 페이지의 contract.options.address에 설정하여 웹 애플리케이션과 연결할 수 있습니다.

3. 웹 페이지에서 스마트 컨트랙트와 상호작용

웹 페이지에서 스마트 컨트랙트와 상호작용하는 방법을 보겠습니다. index.js 파일에서 Remix에서 배포한 스마트 컨트랙트와 연결하려면 contract.options.address를 배포된 스마트 컨트랙트 주소로 설정합니다.

index.html

<!DOCTYPE html>
<html>
<head>
    <title>디입</title> 
    <!-- 웹페이지 제목 설정 -->

    <link href='https://fonts.googleapis.com/css?family=Open Sans:400,700' rel='stylesheet' type='text/css'>
    <!-- Google Fonts에서 Open Sans 폰트 불러오기 -->

    <link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
    <!-- Bootstrap CSS 불러오기 (버전 3.3.7) -->
</head>
<body class="container">
    <!-- Bootstrap의 container 클래스를 사용하여 페이지 레이아웃을 구성 -->

    <h1>심플 투표</h1> 
    <!-- 페이지 제목 -->

    <div class="table-responsive">
        <!-- 반응형 테이블을 위한 div -->

        <table class="table table-bordered">
            <!-- 테두리가 있는 Bootstrap 테이블 -->

            <thead>
                <tr>
                    <th>Candidate</th>
                    <th>Votes</th>
                </tr>
            </thead>
            <!-- 테이블 헤더 (열 제목) -->

            <tbody>
                <tr>
                    <td>Rama</td>
                    <td id="candidate-1"></td>
                </tr>
                <tr>
                    <td>Nick</td>
                    <td id="candidate-2"></td>
                </tr>
                <tr>
                    <td>Jose</td>
                    <td id="candidate-3"></td>
                </tr>
            </tbody>
            <!-- 각 후보자의 이름과 투표 수를 표시할 테이블 본문 -->
        </table>
    </div>

    <input type="text" id="candidate" />
    <!-- 사용자가 후보자 이름을 입력할 수 있는 텍스트 입력 필드 -->

    <a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
    <!-- 사용자가 투표할 수 있는 버튼 -->
</body>

<!-- Web3 라이브러리 (Ethereum 블록체인과 상호작용을 위해 사용) -->
<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js@1.2.6/dist/web3.min.js"></script>

<!-- jQuery 라이브러리 (DOM 조작 및 이벤트 처리를 위해 사용) -->
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>

<!-- 외부 JavaScript 파일 (index.js) 불러오기 -->
<script src="./index.js"></script>

</html>

index.js

web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"))
// Web3 인스턴스를 생성하고, Ganache (로컬 이더리움 블록체인)와 연결

var account;
// 사용자의 계정을 저장할 변수 선언

web3.eth.getAccounts().then((f) => {
    account = f[0];
});
// 이더리움 네트워크에서 사용 가능한 계정 목록을 가져와 첫 번째 계정을 저장

abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]')
// 스마트 컨트랙트의 ABI (Application Binary Interface) 정의를 JSON 형식으로 변환하여 저장

contract = new web3.eth.Contract(abi);
// Web3를 사용하여 스마트 컨트랙트 인스턴스 생성

contract.options.address = "0xbf660D72d597Db3DfB58A48fEbC61961B25f015F";
// 스마트 컨트랙트의 배포된 주소 설정 (실제 배포된 주소로 변경 필요)

candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"};
// 후보자 이름과 HTML ID 매핑을 저장하는 객체 생성

function voteForCandidate(candidate) {
    candidateName = $("#candidate").val();
    // 입력 필드에서 사용자가 입력한 후보자 이름을 가져옴

    console.log(candidateName);
    // 입력된 후보자 이름을 콘솔에 출력

    contract.methods.voteForCandidate(web3.utils.asciiToHex(candidateName)).send({from: account}).then((f) => {
        // 스마트 컨트랙트의 voteForCandidate 함수를 호출하여 투표 실행
        // 문자열을 bytes32 형식으로 변환 후 실행
        let div_id = candidates[candidateName];
        // 후보자 이름을 기반으로 해당 후보자의 HTML ID 가져오기

        contract.methods.totalVotesFor(web3.utils.asciiToHex(candidateName)).call().then((f) => {
            $("#" + div_id).html(f);
            // 최신 투표 수를 가져와 해당 HTML 요소에 표시
        })
    })
}

$(document).ready(function() {
    // 페이지가 로드되면 실행

    candidateNames = Object.keys(candidates);
    // 후보자 목록 가져오기

    for(var i = 0; i < candidateNames.length; i++) {
        let name = candidateNames[i];
        // 각 후보자에 대해 실행

        contract.methods.totalVotesFor(web3.utils.asciiToHex(name)).call().then((f) => {
            $("#" + candidates[name]).html(f);
            // 스마트 컨트랙트에서 해당 후보자의 현재 투표 수를 가져와 HTML에 표시
        })
    }
});

이제 index.html을 브라우저에서 열고, 후보자의 이름을 입력한 뒤 투표하면 블록체인에서 투표 수가 증가하는 것을 확인할 수 있습니다.

5. 마무리 및 요약

우리는 다음과 같은 과정을 통해 블록체인 기반 투표 시스템을 구축했습니다:

  • Ganache를 활용한 개발 환경 설정
  • Solidity 스마트 컨트랙트 작성 및 배포
  • Web3.js를 활용하여 블록체인과 상호 작용하는 웹 애플리케이션 개발

이제 블록체인 기술을 활용한 더 많은 프로젝트를 개발해볼 수 있습니다. 향후에는 이더리움 메인넷에 배포하고 MetaMask 지갑과 연동하는 방법을 다룰 수도 있습니다.

728x90