dukDukz
[블록체인] 투표앱 만들기 본문
# 투표앱 만들기
* 솔리디티
1. 후보자를 초기화
2. 후보자 투표 기능
3. 후보자 정보 가져오기
구현하고 빌드해서 블럭에 배포(실행 시킨다는것)를 한다.
처음 배포할 때 이외에는 쓰지 않음
코드를 작성한 것.
컴파일 하면 두개의 파일이 나오는데 이것들을 블럭에 저장한다. (안에 내용들이 다 담겨 있음)
* 블럭
abi 에 기능들이 다 저장이 됨
주소값도 영수증처럼 생김.
* web3
이 저장한 내용을 웹에다가 출력 해야한다.
이때 쓰는게 바로 web3 이다.
사실 web3 는 따로 빠져있는게 아니라 html 페이지 안에 존재하는거임
얘가 가나쉬에 접근해서 접속한다.
블럭에서 주소값에 대한 내용도 가져와야 한다.
살짝 어려운 부분은 web3 랑 가나쉬랑 연결하는 부분..?
# 투표앱
1. 솔리디티 코드 작성
2. 솔리디티 코드 컴파일
3. 컴파일 결과를 배포 = 블럭에 내용을 추가한다.
4. 컴파일 결과를 js 코드로 내용을 불러올 수 있는지 테스트
5. html 에서 js web3 를 활용해서 내용을 불러온다.
터미널 켜고 (가나쉬 / 작업공간 총 두 개 열어줌)
# 가나쉬 터미널
$ ganache-cli --host 0.0.0.0
# 솔리디티 코드 작성
Voting.sol 생성
mapping(string => uint) public voteReceived;
voteReceieved 는 mapping 으로 선언해주었다.
형태는 이렇게 될 것이다.
let voteReceived = {
ingoo1 : 0,
ingoo2 : 0,
ingoo3 : 0,
[string] [uint]
}
접근하는 방법
voteReceived.ingoo1
voteReceieved[ingoo1]
그래서 투표를 한번 하면
여기서 ingoo1 라는 값에 숫자를 +1 해주면 됨
voteReceieved[ingoo1] = voteReceieved[ingoo1] + 1;
이렇게 선언하고 사용할때는 함수에서
function voteForCandidate(string memory _candidate) public{
voteReceived[_candidate] += 1;
}
memory 는 파일에 저장하지 않겠다
storage 파일에 저장하겠다.
# 예외처리 ; 문자열 비교
1. js ver
let arr = ['ingoo1','ingoo2','ingoo3']
let searchText = 'ingoo4'
function check(searchText){
// 완전탐색 - 모든 배열을 다 검사한다는.
let result = arr.map((v,k)=>{
if(searchText == v){
return true;
}
return false;
})
return result;
혹은
for(let i=0; i<arr.length; i++){
if(arr[i] == searchText){
return true;
}
}
return false;
}
2. sol ver
솔리디티는 문자열 비교가 안되니까..
객체 지향적 사고... 생각이 완료되어야 작성 할 수 있음...
let arr = ['ingoo1','ingoo2','ingoo3']
let searchText = 'ingoo4'
function validCandidate(string memory _candidate) view public returns(bool){
// string 끼리 비교가 안되니까
// string to byte 로 바꿔주고
// keccake256() 매서드 안에 byte 값 넣기
for(uint i=0; i < candidateList.length; i++){
if(keccak256(bytes(candidateList[i])) == keccak25(bytes(_candidate))){
return true;
}
}
return false;
}
# 솔리디티 컴파일
abi, bin 파일 두개가 나옴
solcjs --abi --bin [파일명]
안되면 앞에 npx
터미널 작업공간에서
$ solcjs --abi --bin Voting.sol
하면 파일 두개가 생성됨
# 스마트 컨트랙트 배포하기
web3 라이브러리 활용해서
deploy 사용했던 부분
deploy.js 파일 만들기
- web3 라이브러리 가져오고
- 현재 사용하고 있는 블록체인 서버에 연결 = 가나쉬
- 배포 작업 위해 Contract 매서드를 사용해서 블럭 생성
- 결과 값을 배포하기(deploy) 매서드
배포할 때는 string 값을 못넣음 그래서 이거를 16진수 값으로 바꿔줄거임
['ingoo1','ingoo2','ingoo3'].map(name=>{
return web3.utils.asciiToHex(name)
})
[['ingoo1','ingoo2','ingoo3'].map(name=>web3.utils.asciiToHex(name))]
대괄호 없으면 return 생략 가능 (문법)
이 deploy.js 가 솔리디티코드를 블럭으로 생성하는..
const Web3 = require('web3')
const fs = require('fs')
const ABI = JSON.parse(fs.readFileSync('./Voting_sol_Voting.abi').toString())
const BYTECODE = fs.readFileSync('./Voting_sol_Voting.bin').toString()
const web3 = new Web3('http://localhost:8545')
// 블럭 생성할 때 솔리디티 컴파일한 abi 파일을 인자값에 넣음
const deployContract = new web3.eth.Contract(ABI) // ABI 는 객체가 되어야함 그래서 JSON parse
deployContract.deploy({
//배포를 할때 byte 값을 넣음
data : BYTECODE,
arguments : [['ingoo1','ingoo2','ingoo3'].map(name=>web3.utils.asciiToHex(name))]
// 배포할 때는 string 값을 못넣음 그래서 이거를 16진수 값으로 바꿔줄거임
// 배포를 할때 인자값을 넣어주는거임.
})
.send({
from : '0xc0130920344A4f84b20AD8F458274b00D577BBc1', // 공개키 - 얘가 주체 Voting.js 코드 전체를 올린다.
gas : 6721975,
})
.then(newContract=>{
console.log(newContract.options.address) // Voting 코드 올린 곳의 주소
})
$ npm init
$ npm i web3
$ node .\deploy.js
결과 : 0xc5f4EeA08888836D8947B3f724510c3A73E86151
가나쉬 터미널에서 이렇게 확인할 수 있다.
잠깐 주석처리 하고
컴파일 결과 배포까지는 된거고
js 로 불러오는거 해봐야함
const Web3 = require('web3')
const fs = require('fs')
const ABI = JSON.parse(fs.readFileSync('./Voting_sol_Voting.abi').toString())
const BYTECODE = fs.readFileSync('./Voting_sol_Voting.bin').toString()
const web3 = new Web3('http://localhost:8545')
// 주소는 Contract created: 여기로
// 해당 블럭 (Voting 코드 올린 곳)의 주소에 접속해야 한다.
const contract = new web3.eth.Contract(ABI,'0xc5f4eea08888836d8947b3f724510c3a73e86151')
// send : 누군가 투표한다
// 주소 10개에 해당하는 녀석들만 투표가 가능하게한다. 투표자가 누구인가 (2번 공개주소로함)
contract.methods.voteForCandidate('ingoo1').send({from:'0x36eB10bAa364188DD13968a547260f96824E142F'})
contract.methods.totalVotesFor('ingoo1').call().then(data=>{
// ingoo1 에 대한 총 투표수를 반환하는 녀석
console.log(data)
})
$ node deploy
계속 해보면 숫자 카운팅이 늘어난다.
# 만약 가나쉬를 껐으면?
어케해야할까??
다시 세팅을 해줘야 할 것들이 있다!
가나쉬는 변수에 저장 해놓은거기 때문에
다시 켜면 리셋된 상태에서 시작해야 한다.
배포부터 시작해야함
deploy.js
에서 deployContract.delploy 부분을 다시 해주되
주소값이 달라졌으니까 그 부분을 바꿔서 해주면 된다.
그러면
Contract created 가 새로 나옴
그럼 저 주소를 index.js 에서
deployAddress 를 이 값으로 변경해준다.
그리고 await send 에서도 투표자의 주소를 새로운것으로 변경해주면 된다.
index.js 에서
//가나쉬 연결을 해야함
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
// 브라우저에서 쓰는 js 에서는 fs 가 안되기때문에 그냥 텍스트로 작성
const ABI = JSON.parse(`[{"inputs":[{"internalType":"string[]","name":"_candidateNames","type":"string[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"candidateList","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_candidate","type":"string"}],"name":"totalVotesFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_candidate","type":"string"}],"name":"validCandidate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_candidate","type":"string"}],"name":"voteForCandidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"voteReceived","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`)
// deployAddress : 배포했던 주소값
const deployAddress = `0xc5f4eea08888836d8947b3f724510c3a73e86151`
let VotingContract = new web3.eth.Contract(ABI, deployAddress)
let candidates = { "ingoo1": "candidate1", "ingoo2": "candidate2", "ingoo3": "candidate3" }
// page load 되면 init 함수 실행
window.addEventListener('DOMContentLoaded', init)
async function init() {
// key 값만 빼와서 배열에 담아준다 => ['ingoo1','ingoo2','ingoo3']
let candidateNames = Object.keys(candidates)
for (let i = 0; i < candidateNames.length; i++) { // 3
let name = candidateNames[i] // ingoo1
/*
candidate1 값을 가져오고 싶다면?
candidates[name]
-> candidates.ingoo1 과 같은 결과 값
-> 뽑아온 이유는 element 에서 id 값 선택 위해서
*/
const nameElement = document.querySelector(`#${candidates[name]}`)
nameElement.innerHTML = name
// 여기까지 작성하고 브라우져로 확인
// 이제 html 에서 cadidateCount 부분에 숫자를 채워줄거임
const countElement = document.querySelector(`#candidateCount${i + 1}`)
countElement.innerHTML = await VotingContract.methods.totalVotesFor(name).call()
}
}
input box에 ingoo1 하면 투표 +1 되고 다시 값 받아와서 화면에 뿌려주는
// 페이지 로드됐을때 하는게 아니니까 init 함수 밖에다 써주고
let btn = document.querySelector(`#btn`)
btn.addEventListener('click',btnEvent)
async function btnEvent(){
let candidateName = document.querySelector('#candidateName').value
await VotingContract.methods.voteForCandidate(candidateName).send({ from: '0x36eB10bAa364188DD13968a547260f96824E142F' })
let candidateCount = await VotingContract.methods.totalVotesFor(candidateName).call()
let number = candidateName.charAt(candidateName.length-1)
let countElement = document.querySelector(`#candidateCount${number}`)
countElement.innerHTML = candidateCount
}
이 부분 추가
'웹 개발 > 블록체인' 카테고리의 다른 글
[블록체인] truffle 에서 react 사용하기 - 1 (0) | 2021.10.13 |
---|---|
[블록체인] 메타마스크란? (0) | 2021.10.12 |
[블록체인] 거래소 기본 개념 (0) | 2021.10.12 |
[블록체인] 스마트 컨트렉트 간단 실습 + Web3 란? (2) | 2021.10.11 |
[블록체인] 솔리디티 언어 연습 (좀비) (0) | 2021.10.11 |