dukDukz
[블록체인] 작업증명 / 난이도 조절 본문
작업증명
- 마이닝, 채굴...
쉽게 말하면 블럭을 생성할 때 쉽게 생성하지 못하도록 하는것.
http 인터페이스를 통해 body 영역에 내용 추가해서 블럭 추가했었는데,
요청을 해서 바로 생성 X
문제를 내고 풀었을때만 (컴퓨터가 자동으로 풀 수 있게끔- 몇번정도 try해서 풀었는지) 블럭 추가할 수 있게.
[block0902.js]
difficulty 와 nonce 추가
-> header 내용이 변경된 것
= BlockHeader 를 썼던 부분을 수정해줘야함 (3군데)
1. BlockHeader() 에 difficulty 와 nonce 추가
this.difficulty = difficulty // 문제 난이도
this.nonce = nonce // 문제 몇번 시도 했는지
해당 코드
class BlockHeader {
constructor(version, index, previousHash, time, merkleRoot, difficulty, nonce) {
this.version = version
this.index = index
this.previousHash = previousHash
this.time = time
this.merkleRoot = merkleRoot
// 0908
this.difficulty = difficulty // 문제 난이도
this.nonce = nonce // 문제 몇번 시도 했는지
}
}
2. createGenesisBlock()
제네시스 블럭 만드는곳에 추가
const difficulty = 0
const nonce = 0
const header = new BlockHeader(version, index, previousHash, time, root, difficulty, nonce)
이 부분 추가 및 수정
function createGenesisBlock() {
const version = "1.0.0"
const index = 0
const time = 1630907567
const previousHash = '0'.repeat(64)
const body = ['hello block']
const tree = merkle('sha256').sync(body)
const root = tree.root() || '0'.repeat(64)
const difficulty = 0
const nonce = 0
const header = new BlockHeader(version, index, previousHash, time, root, difficulty, nonce)
return new Block(header, body)
}
3. findBlock 함수를 생성해줌
BlockHeader 랑 인자값 똑같이 받음
nextBlock() 에
const difficulty = 0 // 추가
...
const header = findBlock(version, index, previousHash, time, merkleRoot, difficulty)
// 수정
difficulty 추가하고
BlockHeadr 에 인자값을 줘서 헤더를 만들었던것을 findBlock() 이라는 함수에 넣어서 만드는것으로 바꿔야 한다.
해당 코드
function nextBlock(data) {
// header
const prevBlock = getLastBlock() // 맨 마지막 블럭의 정보를 가져옴
const version = getVersion()
const index = prevBlock.header.index + 1
const previousHash = createHash(prevBlock)
const time = getCurrentTime()
const difficulty = getDifficulty(getBlocks()) //<- 함수를 만들어야함..
const merkleTree = merkle("sha256").sync(data)
const merkleRoot = merkleTree.root() || '0'.repeat(64)
const header = findBlock(version, index, previousHash, time, merkleRoot, difficulty)
return new Block(header, data)
}
findBlock 함수 생성
function findBlock(version, index, previousHash, time, merkleRoot, difficulty){
let nonce = 0
return new BlockHeader(version,index,previousHash,time,merkleRoot,difficulty,nonce)
}
4. findBlock() 안에서 검증처리 - 무한 반복해야함
while (true) {
if (1 == 1) {
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce ++
}
검증 처리 하는 부분 추가
해당 코드
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0
// 여기서 검증처리 해야함 - 무한 반복 돌거임 - 조건이 맞아야만 빠져나올 수 있도록
while (true) {
// 이 상태에서는 block 이 없다.
// 여기서 createHeaderHash 함수 호출할거임 그래서 인자값으로 nonce 값 줄 수 있음
let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce)
console.log(hash);
console.log('-----------');
if (hashMatchDifficulty(hash, difficulty)) {
// 우리가 앞으로 만들 header의 hash 값의 앞자리 0이 몇개인가?
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++
}
}
코드 이해 위해서 sample code
text.js 생성
const CryptoJs = require('crypto-js')
let a = "0000helloworld!"
// 첫글자 4개가 0000 이 맞냐? - 어떻게 검증해야 할까?
// js string 관련 문법중에 startsWith 이 있음
console.log(a.startsWith("0000")); // true 가 나온다.
console.log(a.startsWith("0001")); // false 가 나온다.
/*
Hash -> SHA256 을 쓸거임
helloworld 를 암호화 할거임
*/
console.log(CryptoJs.SHA256(a).toString().toUpperCase()); // 소문자에서 대문자까지 바꿔줌
// HASH 값 기준으로 돌릴거임.. 저 반복문
// 내가 첫글자가 0이 4개가 되었을 때 블럭을 생성할 수 있도록 작업할것이다.
1. 현재 hash 값의 결과물은 몇진수일까?? 16진수이다.
2. 16진수와 2진수는 밀접한 관계 (변환이 쉬움)
내 결과물 -> SHA256 으로 변환 (16진수) -> 2진수로 변경할 것이다.
첫글자 16진수 1일때 2진수가 무엇이냐? 이런식으로 16개의 case 를 구해서
SHA256의 hash 값을 2진수로 바꾸는 작업을 할 것이다.
0~F (16진수) -> 2진수 0000 0001 0010 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1111 ...
a의 hash 값은
1314042ECF8C8A7702AABA1C82D560B5A262FF3E922BB117FA81F2B002FC37B9
0001 0100
이런식으로 가게 됨
첫글자 2진수로 만들었을 때, 2개가 0이다 하면 0과 1
1개가 0이다 하면, 0~7까지
5. 조건 생성 위해서
utils.js 생성
6. hexToBinary() 생성
이 함수의 궁극적인 목표 : 합쳐주기
function hexToBinary(s){
const lookup ={
"0" : "0000", "1" : "0001", "2" : "0010", "3" : "0011",
"4" : "0100", "5" : "0101", "6" : "0110", "7" : "0111",
"8" : "1000", "9" : "1001", "A" : "1010", "B" : "1011",
"C" : "1100", "D" : "1101", "E" : "1110", "F" : "1111",
}
let rst =""
for(let i=0; i<s.length; i++){
if(lookup[s[i]] === undefined) return null
rst += lookup[s[i]]
}
return rst
}
module.exports = {
hexToBinary
}
# 한 글자씩 가져오는건 어떻게 해야할까??
console.log(s[1]);
이런식으로 하면 된다.
# 총 글자수를 구해오는 방법..
console.log(s.length);
# 한글자씩 검사
for(let i=0; i<s.length; i++){
console.log(s[i]) //16진수로 리턴해줌
}
여기서 한글자씩 리턴해준 값을 2진법으로 바꿔야한다.
- lookup 객체에서 내 인자값을 넣어서 찾아오면 됨
console.log(lookup.F);
이거나
console.log(lookup["F"]);
- 이걸 for 문안에 넣으면?
for(let i=0; i<s.length; i++){
console.log(lookup[ s[i] ]) //2진수로 변환
}
근데 hash 값을 잘못만든경우.
0~F 이외의 다른 문자가 있는경우
undefined 가 나옴
그래서 for 문 안에서 예외처리까지 하는것
for(let i=0; i<s.length; i++){
if(lookup[s[i]] === undefined) return null
console.log(lookup[s[i]])
}
[block.js]
createHash 써야함
function nextBlock(data) {
const header = findBlock(version, index, previousHash, time, merkleRoot, difficulty)
}
difficulty 추가해서 바꿔줌
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0
// 여기서 검증처리 해야함 - 무한 반복 돌거임 - 조건이 맞아야만 빠져나올 수 있도록
while (true) {
// 이 상태에서는 block 이 없다.
// 여기서 createHeaderHash 함수 호출할거임 그래서 인자값으로 nonce 값 줄 수 있음
let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce)
if (hashMatchDifficulty(hash, difficulty)) {
// 우리가 앞으로 만들 header의 hash 값의 앞자리 0이 몇개인가?
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++
}
}
findBlock() 에서는 block 이 없어서
함수를 하나 만들어줘야함
createHash 함수를 사용못함
createHeaderHash() 함수를 생성
function createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce) {
let txt = version + index + previousHash + time + merkleRoot + difficulty + nonce
return CryptoJs.SHA256(txt).toString().toUpperCase()
}
if 문 안에 들어가는..
내 hash 2진수로 바꾸고 첫글자 4개가 0인가를 조건으로 넣어줘야함
근데 if 한줄에 넣기에는 양이 많아보임
함수로 빼주자 - hashMatchDifficulty() 생성
// 두 가지 인자값을 받음 hash 와 difficulty(= 0이 몇개인지에 따라 난이도가 달라짐)
function hashMatchDifficulty(hash, difficulty){
// hash 16진수 -> 2진수
const hashBinary = hexToBinary(hash) // 아까 만든 함수에 hash 를 넣어서 16->2 진수로 변경함
const prefix = '0'.repeat(difficulty) // difficulty 갯수만큼 0 반복
return hashBinary.startsWith(prefix) // 얘의 결과 는 boolean 형태
}
컴퓨터가 문제를 풀고 블럭을 생성하는..
블럭이 많아질수록 난이도가 올라가야함...
difficulty 처리
1분에 하나 이상 만들수 없게 한다거나...
이런식으로... 기준점이 각각 다름..
총 블럭, 총 코인의 개수
시간, 블럭의 개수 정도를 따져서 만들어보자
nextBlock() 부분
const difficulty = getDifficulty(getBlocks())
추가 하고
전체 코드
function nextBlock(data) {
// header
const prevBlock = getLastBlock() // 맨 마지막 블럭의 정보를 가져옴
const version = getVersion()
const index = prevBlock.header.index + 1
const previousHash = createHash(prevBlock)
const time = getCurrentTime()
const difficulty = getDifficulty(getBlocks()) //<- 함수를 만들어야함..
const merkleTree = merkle("sha256").sync(data)
const merkleRoot = merkleTree.root() || '0'.repeat(64)
const header = findBlock(version, index, previousHash, time, merkleRoot, difficulty)
return new Block(header, data)
}
[block.js] 에 추가하기
// 블럭의 개수의 따라서 난이도 조정
const BLOCK_GENERATION_INTERVAL = 10 //10초 기준
const BLOCK_ADJUSTIMENT_INTERVAL = 10 // 블럭이 10개가 넘을 때마다 난이도 변경
(제네시스 블럭 제외하고 10번째)
10으로 나눈 나머지 값이 0 일때 -> 10의 배수
function getDifficulty(blocks) {
// 시간에 관련되서 처리
// 마지막 블럭가져오기
const lastBlock = blocks[blocks.length - 1]
if (lastBlock.header.index % BLOCK_ADJUSTIMENT_INTERVAL === 0 && lastBlock.header.index !== 0) {
// 10으로 나눈 값의 나머지가 0 이 되었을 때 -> 난이도 조정 && 제네시스 블럭이 아니어야함
return getAdjustedDifficulty(lastBlock, blocks)
// 마지막 배열과 내가 전체 갖고있는 블럭을 인자값으로 던져야함
}
return lastBlock.header.difficulty
// 조건 해당없으면 이전블럭의 난이도와 동일
}
getAdjustedDifficulty 생성
하나의 블럭에 10초 간격으로 생성가능? + 10개마다 난이도 증가
block 10개 단위로 끊는다. - 게시판의 페이징처럼 - 이전의 값
[difficulty 조절]
lastblock 의 난이도를 가져오면 좀 힘들어지는게...
-> difficulty + 1 하게되면?
10번째 블럭이 되어서 난이도 조정해야 하는 상황이라면
11 번째 난이도 증가 시켜야 하는데 이전값 +1 해버리면 안된다.
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
아예 열번째 전에 있는거를 가져오면 된다.
11번은 1번째 블럭 난이도 +1 하라는 얘기
function getAdjustedDifficulty(lastBlock, blocks){
// 하나의 블럭에 10초 간격으로 생성가능? + 10개마다 난이도 증가
// block 10개 단위로 끊는다. - 게시판의 페이징처럼 - 이전의 값
// lastblock 의 난이도를 가져오면 좀 힘들어지는게...
const prevAdjustmentBlock = blocks[blocks.length - BLOCK_ADJUSTIMENT_INTERVAL]
// 총 배열 20개 -10 하면 10번째 애를 가져오게됨
// 시간 관련된 녀석
const timeToken = lastBlock.header.time - prevAdjustmentBlock.header.time
// 그러면 시간이 얼마만큼 차이가 나는지 알 수 있다.
}
10개 블럭을 만들었을 때 걸리는 시간은 몇 초일까?
- 하나 만들때 10초로 세팅했기 때문에 100초 걸릴것이다.
function getAdjustedDifficulty(lastBlock, blocks) {
const prevAdjustmentBlock = blocks[blocks.length - BLOCK_ADJUSTIMENT_INTERVAL]
// 총 배열 20개 -10 하면 10번째 애를 가져오게됨
const timeToken = lastBlock.header.time - prevAdjustmentBlock.header.time
// 그러면 시간이 얼마만큼 차이가 나는지 알 수 있다.
const timeExpected = BLOCK_ADJUSTIMENT_INTERVAL * BLOCK_GENERATION_INTERVAL
// 예상시간 100초
// 예상시간보다 빨리만들었으면 난이도 올림
if (timeToken < timeExpected / 2) {
return prevAdjustmentBlock.header.difficulty + 1
} else if (timeToken > timeExpected * 2) {
// 예상 시간보다 너무 느린데 싶으면 난이도 낮춤
return prevAdjustmentBlock.header.difficulty - 1
} else {
// 예상시간대로 잘 가고 있으면 그대로
return prevAdjustmentBlock.header.difficulty
}
}
findBlock 에서
console.log(hash);
console.log('-----------');
이렇게 추가하고
node server 해서
한번 보면
계속 추가하다가
난이도가 4가 되는 시점에 hash 값 앞이 0이 되는
10마다 difficulty +1 이 되는
'웹 개발 > 블록체인' 카테고리의 다른 글
[블록체인] 추가 개념 (0) | 2021.09.09 |
---|---|
[블록체인] 지갑 만들기 (0) | 2021.09.09 |
[블록체인] 남의 서버에 연결 및 데이터 추가 (0) | 2021.09.07 |
[블록체인] network.js 상세설명 (0) | 2021.09.07 |
[블록체인] network.js (callback) (0) | 2021.09.07 |