1. 개요
오늘은 Solidity 스마트 컨트랙트와 Web3.js를 활용하여 블록체인 기반 투표 시스템을 구축하는 과정을 학습했다. Solidity로 작성된 스마트 컨트랙트와 이를 프론트엔드에서 다루는 JavaScript 코드를 분석하고 이해하는 것이 목표였다.
2. 스마트 컨트랙트 (Migrations.sol
)
먼저, Truffle을 사용하여 마이그레이션을 관리하는 스마트 컨트랙트를 작성했다.
pragma solidity ^0.5.0; // Solidity 컴파일러 버전 지정
contract Migrations { // 마이그레이션을 관리하는 스마트 컨트랙트 선언
address public owner; // 컨트랙트 소유자의 주소 저장
uint public last_completed_migration; // 마지막으로 완료된 마이그레이션의 번호 저장
// 제한된 접근을 위한 modifier 정의 (owner만 실행 가능)
modifier restricted() {
if (msg.sender == owner) _; // 호출자가 owner일 경우 함수 실행
}
// 컨트랙트 생성자, 배포 시 실행됨
constructor () public {
owner = msg.sender; // 배포한 사람(계정)을 owner로 설정
}
// 마이그레이션 완료 상태를 설정하는 함수
function setCompleted(uint completed) public restricted {
last_completed_migration = completed; // 마이그레이션 번호 업데이트
}
// 새로운 마이그레이션 컨트랙트로 업그레이드하는 함수
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address); // 새로운 컨트랙트 인스턴스 생성
upgraded.setCompleted(last_completed_migration); // 이전 마이그레이션 상태를 새 컨트랙트로 전달
}
}
학습한 내용
modifier restricted()
를 사용하여 owner만 특정 기능을 수행할 수 있도록 제한했다.upgrade(address new_address)
를 통해 새로운 마이그레이션을 적용할 수 있다.setCompleted(uint completed)
를 사용하여 마이그레이션 완료 상태를 업데이트한다.
3. 프론트엔드 (app.js
)
Web3.js와 Truffle-contract를 활용하여 스마트 컨트랙트와 상호작용하는 JavaScript 코드를 작성했다.
// CSS 파일을 불러와 스타일 적용
import "../stylesheets/app.css";
// Web3 라이브러리 불러오기 (Ethereum 블록체인과 상호작용하기 위해 필요)
import { default as Web3} from 'web3';
// Truffle-contract 라이브러리 불러오기 (스마트 컨트랙트와 상호작용을 쉽게 하기 위해 사용)
import { default as contract } from 'truffle-contract';
// 컴파일된 스마트 컨트랙트 정보를 JSON 파일에서 가져오기
import voting_artifacts from '../../build/contracts/Voting.json';
// Voting 스마트 컨트랙트 객체 생성
var Voting = contract(voting_artifacts);
// 후보자 목록과 해당 후보자를 표시할 HTML 요소의 ID 매핑
let candidates = {
"Rama": "candidate-1",
"Nick": "candidate-2",
"Jose": "candidate-3"
};
let account; // 사용자의 이더리움 계정을 저장할 변수
// 사용자가 특정 후보자에게 투표하는 함수
window.voteForCandidate = function(candidate) {
let candidateName = $("#candidate").val(); // 사용자가 입력한 후보자 이름 가져오기
try {
// 사용자에게 메시지를 표시하여 투표가 진행 중임을 알림
$("#msg").html("Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.");
$("#candidate").val(""); // 입력 필드 초기화
// 배포된 스마트 컨트랙트 인스턴스를 가져와서 실행
Voting.deployed().then(function(contractInstance) {
// 스마트 컨트랙트의 voteForCandidate 함수 호출 (가스를 지정하고 사용자 계정에서 실행)
contractInstance.voteForCandidate(candidateName, {gas: 140000, from: account}).then(function() {
let div_id = candidates[candidateName]; // 후보자의 ID를 가져옴
// 블록체인에서 해당 후보자의 총 투표 수 가져오기
return contractInstance.totalVotesFor.call(candidateName).then(function(v) {
$("#" + div_id).html(v.toString()); // UI 업데이트
$("#msg").html(""); // 메시지 초기화
});
});
});
} catch (err) {
console.log(err); // 오류 발생 시 콘솔에 출력
}
};
// 페이지가 로드될 때 실행되는 함수
$(document).ready(function () {
if (typeof web3 !== 'undefined') { // 사용자의 브라우저에 Web3 인스턴스가 있는지 확인
if (window.ethereum) { // 최신 Metamask 브라우저 확장 프로그램을 지원하는 경우
web3 = new Web3(window.ethereum);
try {
// 사용자에게 계정 접근 권한 요청
window.ethereum.enable();
} catch (error) {
console.error("User denied account access"); // 사용자가 권한 요청을 거부한 경우
}
} else if (window.web3) { // 이전 버전의 Metamask 지원
web3 = new Web3(window.web3.currentProvider);
} else { // 사용자가 Metamask를 설치하지 않은 경우, 로컬 Ganache 노드에 연결
web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
}
}
//////////////////////////////////////////////////////////////////
// 사용자의 이더리움 계정을 가져옴
web3.eth.getAccounts(function (err, accs) {
if (err != null) {
alert('There was an error fetching your accounts.'); // 계정 가져오기 실패 시 알림
return;
}
if (accs.length === 0) { // 계정이 없는 경우
alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly.");
return;
}
account = accs[0]; // 첫 번째 계정을 기본 계정으로 설정
});
// 스마트 컨트랙트를 Web3의 현재 공급자와 연결
Voting.setProvider(web3.currentProvider);
// 후보자들의 초기 투표 수를 블록체인에서 가져와 화면에 표시
let candidateNames = Object.keys(candidates); // 후보자 이름 목록 가져오기
for (var i = 0; i < candidateNames.length; i++) {
let name = candidateNames[i];
Voting.deployed().then(function(contractInstance) {
contractInstance.totalVotesFor.call(name).then(function(v) {
$("#" + candidates[name]).html(v.toString()); // 투표 수를 UI에 업데이트
});
});
}
});
학습한 내용
Web3.js
를 활용하여 이더리움 네트워크와 연결하는 방법을 익혔다.window.ethereum.enable();
을 사용하여 Metamask와 상호작용하는 방법을 학습했다.voteForCandidate()
함수에서 투표 기능을 구현하고 UI를 업데이트하는 방법을 배웠다.
4. 결론
✔ Solidity 스마트 컨트랙트를 작성하고 배포하는 방법을 익혔다.
✔ Web3.js를 활용하여 프론트엔드에서 스마트 컨트랙트와 상호작용하는 방법을 이해했다.
✔ Metamask와 같은 지갑을 이용해 블록체인과 연동하는 방법을 배웠다.
다음 학습 목표
🔹 투표 시스템의 보안성을 강화하는 방법 (예: 중복 투표 방지)
🔹 이벤트를 활용하여 실시간으로 UI 업데이트 구현
🔹 프론트엔드를 React로 개선하여 더 나은 UX 제공
오늘 배운 내용들을 바탕으로 블록체인 기반 완전한 탈중앙화 투표 시스템을 구축하는 것이 목표! 🚀
'Decentralization' 카테고리의 다른 글
가스비 걱정 없이 블록체인 애플리케이션을 개발하는 방법 (5) | 2025.03.22 |
---|---|
Solidity로 기본적인 토큰 계약 구현하기 토큰 발행, 전송, 구매 및 판매 기능 (1) | 2025.03.17 |
Win & Ubuntu CLI에서 Truffle 설정 및 스마트 계약 배포 가이드 (1) | 2025.03.17 |
Solidity로 기본적인 토큰 판매 스마트 컨트랙트 작성 (1) | 2025.03.15 |
Solidity 스마트 컨트랙트: SimpleAuction 경매 시스템 (1) | 2025.03.15 |