오늘 작업증명 공부 예정 !
block.js 을 주로 수정할 예정
-> 블럭 생성할 때 쉽게 생성하지 못하도록 만들기
-> 요청한대로 블록이 바로 생성되는게 아니라 문제를 풀어서 생성하도록
작업증명이란 ?
컴퓨터 연산을 통해 일정한 hash를 찾도록 하는 작업증명(pow)과정을 거치도록 한다. 작업증명을 통해 특정 해시값을 찾기위해 수많은 반복연산을 수행하도록 함으로써 상당한 시간과 비용이 들게 해서 결국 대량 스팸메일을 보낼 수 없도록 하는게 최초 탄생 이유 (이후 비트코인에 적용됨 by 사토시 나카모토)
1. Header 기존 5가지 내용에 + 2 두 가지 추가
- 문제 난이도 설정
- 문제 몇번 풀었는지에 대한
block.js
class BlockHeader {
constructor(version ,index ,previousHash, time, merkleRoot, difficulty, nounce){
this.version = version
this.index = index // 마지막 블럭의 index + 1
this.previousHash = previousHash // 마지막 블럭 -> header -> string 연결 -> SHA256
this.time = time //
this.merkleRoot = merkleRoot
// 문제 어려움 정도
this.difficulty = difficulty
// 풀은 횟수
this.nounce = nounce
}
}
2. GENESISBLOCK - new BlockHeader 사용한 createGenesisBlock 수정
function createGenesisBlock(){
// 1. header 만들기
// 5개의 인자값을 만들어야되여.
const version = "1.0.0" // 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 nounce = 0
const header = new BlockHeader(version,index,previousHash,time,root, difficulty, nounce)
return new Block(header,body)
}
난이도 0 / nounce 0 설정
3. nextBlock() 함수에도 쓰인 BlockHeader 수정
nextBlock()에서 nounce 설정 등등이 이루어질 것 !
nonce
-findBlock() 생성 - > 여기에서 header 생성할 것 -> new BlockHeader => 방금 만든 findBlock()으로 대체 + difficulty 일단 0 으로 추가 (먼저 nonce부터 처리하고 difficulty 처리 예정)
// 다음블럭의 Header와 Body를 만들어주는 함수 2번
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 = 0
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)
}
function findBlock(version,index,previousHash,time,merkleRoot,difficulty){
let nonce = 0
return new BlockHeader(version,index,previousHash,time,merkleRoot,difficulty, nonce)
}
이제 findBlock() 함수 안에 무한반복문을 넣고 이제 조건이 맞을 때만 return 되도록 만들기
반복할 때마다 nonce 값 ++
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0
while (1) {
if () {
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++;
}
}
if () 조건 들어가는 것 만들기
샘플 만들기 (연습용)
src > test.js
const CryptoJs = require('crypto-js')
let a = "0000helloThere!"
console.log(a.startsWith('0000'))
console.log(CryptoJs.SHA256(a))
console.log(CryptoJs.SHA256(a).toString())
console.log(CryptoJs.SHA256(a).toString().toUpperCase())
첫글자가 0000 "0"이 네개가 되었을 때 블럭을 생성할 수 있도록 작업
- SHA256으로 16진수로 변환된 것 => 2진수로 변환할 것
16진수 0 = 2진수 0000
즉, 2진수로 바꿨을 때 0000인 경우 (16진수일 때 첫 글자가 0000일때) 확율 1/16
블록에 따라 0000의 개수를 가변적으로 만들면 나중에 확율이 많이 낮아져서 컴퓨터가 for문을 막 돌림!
src > utils.js 파일 생성
//131402EA24........
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",
}
console.log(s)
}
const txt = "1314042ECF8C8A7702AABA1C82D560B5A262FF3E922BB117FA81F2B002FC37B9";
hexToBinary(txt)
//131402EA24........
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",
}
console.log(s)
for(let i=0; i<s.length; i++){
console.log(lookup[s[i]])
}
}
const txt = "1314042ECF8C8A7702AABA1C82D560B5A262FF3E922BB117FA81F2B002FC37B9";
hexToBinary(txt)
hash에 있어서는 안되는 글자가 있으면 안됨 !! 예외처리 해주기
//131402EA24........
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",
}
console.log(s)
let rst = ""
for(let i=0; i<s.length; i++){
if(lookup[s[i]] === undefined) return null
console.log(lookup[s[i]])
rst += lookup[s[i]]
}
return rst
}
const txt = "1314042ECF8C8A7702AABA1C82D560B5A262FF3E922BB117FA81F2B002FC37B9";
let result = hexToBinary(txt)
console.log(result)
module.exports={
hexToBinary
}
exports 해주기
createHeaderHash함수 만들기 (findBlock 함수 완성위한 빌드작업)
// 요 함수가 쓰일 때 nonce가 위에 설정되어있어서 nonce값도 넣어주기
function createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce){
let txt = version+index+previousHash+time+merkleRoot+difficulty;
return CryptoJs.SHA256(txt).toString().toUpperCase()
}
toUpperCase 이유 : 아까 utils.js 에서 대문자로 설정함 ! 맞춰주기
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0;
while (1) {
// 조건 : 우리가 만들 header의 hash값의 앞자리 0이 몇개인가
let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce);
// 내가 가지고있는 hash에서 그 값을 2진수로 바꾸고
// 2진수에서 바꾼 값 중에서 첫글자가 0000인가~
if () {
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++;
}
}
이제 if () 조건절에 가지고 있는 hash 에서 2진수로 바꾸고 그 값의 첫글자가 0000인가 를 확인할건데 또 길꺼니까 함수로 뺌
16진수 -> 2진수로 바꾸는 utils.js 가져와서 적용
const {hexToBinary} = require('./utils')
function hashMatchDifficulty(hash, difficulty){
//difficulty 에 따라 0의 개수가 다름
//difficulty 가 높아질 수록 의 개수가 올라가서 =>
// 난이도가 어려워짐
// hash(16진수) => 2진수로 바꾸기
const hashBinary = hexToBinary(hash)
}
function hashMatchDifficulty(hash, difficulty){
//difficulty 에 따라 0의 개수가 다름
//difficulty 가 높아질 수록 의 개수가 올라가서 =>
// 난이도가 어려워짐
// hash(16진수) => 2진수로 바꾸기
const hashBinary = hexToBinary(hash)
// startsWith() return Boolean
const prefix = '0'.repeat(difficulty)
return hashBinary.startsWith(prefix)
}
드디어 findBlock 완성 ㅋ..
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0;
while (1) {
// 조건 : 우리가 만들 header의 hash값의 앞자리 0이 몇개인가
let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce);
// 내가 가지고있는 hash에서 그 값을 2진수로 바꾸고
// 2진수에서 바꾼 값 중에서 첫글자가 0000인가~
if (hashMatchDifficulty(hash,difficulty)) {
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++;
}
}
function hashMatchDifficulty(hash, difficulty){
//difficulty 에 따라 0의 개수가 다름
//difficulty 가 높아질 수록 의 개수가 올라가서 =>
// 난이도가 어려워짐
// hash(16진수) => 2진수로 바꾸기
const hashBinary = hexToBinary(hash)
// startsWith() return Boolean
const prefix = '0'.repeat(difficulty)
return hashBinary.startsWith(prefix)
}
block.js 전체
const fs = require('fs')
const merkle = require('merkle')
const CryptoJs = require('crypto-js')
const random = require('random')
const {hexToBinary} = require('./utils')
/* 사용법 */
// const tree = merkle("sha256").sync([]) // tree 구조
// tree.root()
class BlockHeader {
constructor(version, index, previousHash, time, merkleRoot, difficulty, nonce) {
this.version = version
this.index = index // 마지막 블럭의 index + 1
this.previousHash = previousHash // 마지막 블럭 -> header -> string 연결 -> SHA256
this.time = time //
this.merkleRoot = merkleRoot
// 문제 어려움 정도
this.difficulty = difficulty
// 풀은 횟수
this.nonce = nonce
}
}
class Block {
constructor(header, body) {
this.header = header
this.body = body
}
}
let Blocks = [createGenesisBlock()]
function getBlocks() {
return Blocks
}
function getLastBlock() {
return Blocks[Blocks.length - 1]
}
function createGenesisBlock() {
// 1. header 만들기
// 5개의 인자값을 만들어야되여.
const version = "1.0.0" // 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)
}
// 다음블럭의 Header와 Body를 만들어주는 함수 2번
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 = 0
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)
}
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0;
while (1) {
// 조건 : 우리가 만들 header의 hash값의 앞자리 0이 몇개인가
let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce);
// 내가 가지고있는 hash에서 그 값을 2진수로 바꾸고
// 2진수에서 바꾼 값 중에서 첫글자가 0000인가~
if (hashMatchDifficulty(hash,difficulty)) {
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++;
}
}
function hashMatchDifficulty(hash, difficulty){
//difficulty 에 따라 0의 개수가 다름
//difficulty 가 높아질 수록 의 개수가 올라가서 =>
// 난이도가 어려워짐
// hash(16진수) => 2진수로 바꾸기
const hashBinary = hexToBinary(hash)
// startsWith() return Boolean
const prefix = '0'.repeat(difficulty)
return hashBinary.startsWith(prefix)
}
// 요 함수가 쓰일 때 nonce가 위에 설정되어있어서 nonce값도 넣어주기
function createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce){
let txt = version+index+previousHash+time+merkleRoot+difficulty;
return CryptoJs.SHA256(txt).toString().toUpperCase()
}
// 3번
function createHash(block) {
const {
version,
index,
previousHash,
time,
merkleRoot
} = block.header
const blockString = version + index + previousHash + time + merkleRoot;
const Hash = CryptoJs.SHA256(blockString).toString()
return Hash
}
// Blocks push 1번
function addBlock(newBlock) {
// new header -> new block ( header , body)
if (isVaildNewBlock(newBlock, getLastBlock())) {
Blocks.push(newBlock);
return true;
}
return false;
}
function mineBlock(blockData) {
const newBlock = nextBlock(blockData) // Object Block {header, body}
if (addBlock(newBlock)) {
const nw = require('./network')
nw.broadcast(nw.responseLastMsg())
return newBlock
} else {
return null
}
}
/* etc
1: 타입검사
*/
function isVaildNewBlock(currentBlock, previousBlock) {
// currentBlock 에대한 header , body DataType을 확인
if (!isVaildType(currentBlock)) {
console.log(`invaild block structrue ${JSON.stringify(currentBlock)}`)
return false
}
// index값이 유효한지
if (previousBlock.header.index + 1 !== currentBlock.header.index) {
console.log(`invaild index`)
return false
}
// previousHash 체크
/*
어떻게 만들었는가?
해당블럭의 header의 내용을 글자로 합쳐서 SHA256 활용하여 암호화한 결과물
previousHash previousHash
제네시스 블럭 기준 -> 2번째 블럭
*/
if (createHash(previousBlock) !== currentBlock.header.previousHash) {
console.log(`invaild previousBlock`)
return false
}
// Body check
/*
current.header.merkleRoot -> body [배열]
current.body -> merkleTree root -> result !== current.header.merkleRoot
굳이왜 ?..
네트워크
body... 내용이 없으면안됩니다.
current.body.lenght !== 0 || ( currnetBlock.body가지고 만든 merkleRoot !== currentBlock.header.merkleRoot )
current.body.lenght !== 0 || ( merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot )
*/
if (currentBlock.body.length === 0) {
console.log(`invaild body`)
return false
}
if (merkle("sha256").sync(currentBlock.body).root() !== currentBlock.header.merkleRoot) {
console.log(`invalid merkleRoot`)
return false
}
return true
}
function isVaildType(block) {
return (
typeof (block.header.version) === "string" && // stirng
typeof (block.header.index) === "number" && // number
typeof (block.header.previousHash) === "string" && // stirng
typeof (block.header.time) === "number" && // number
typeof (block.header.merkleRoot) === "string" && // string
typeof (block.body) === "object" // object
)
}
function replaceBlock(newBlocks) {
// newBlocks : 내가 받은 전체 배열 => 내가 받은 전체 블록들
//Blocks = newBlocks
// 1. newBlocks 내용을 검증해야합니다.
// 2. 검증을 한번만 하지않습니다. 랜덤하게 한번만할수있고, 두번할수있고, 세번할수도있게합니다. -> 조건문에 random을 사용한다.
// 3. Blocks = newBlocks
// 4. broadcast 날립니다.
if (isVaildBlock(newBlocks) && newBlocks.length > Blocks.length && random.boolean()) {
console.log(`Blocks 배열을 newBlocks 으로 교체합니다.`)
const nw = reuqire('./network')
Blocks = newBlocks
nw.broadcast(nw.responseLastMsg())
} else {
console.log(`메시지로 부터 받은 블록배열이 맞지 않습니다.`)
}
}
function getVersion() {
const { version } = JSON.parse(fs.readFileSync("../package.json"))
return version
}
function getCurrentTime() {
return Math.ceil(new Date().getTime() / 1000)
}
/*
일단 제네시스 블럭이 유효한지 데이터가 바뀐적이 없는지.
2번째는
blocks 모든 배열을 검사를 할겁니다.
*/
function isVaildBlock(Blocks) {
if (JSON.stringify(Blocks[0]) !== JSON.stringify(createGenesisBlock())) {
console.log(`genesis error`)
return false
}
//Blocks 3개
// 1 < 3
let tempBlocks = [Blocks[0]]
for (let i = 1; i < Blocks.length; i++) {
if (isVaildNewBlock(Blocks[i], tempBlocks[i - 1])) {
tempBlocks.push(Blocks[i])
} else {
return false
}
}
return true
}
// class
// { header body } 1차 목표는 제네시스 블럭을 만드는것
//console.log(Blocks)
module.exports = {
getBlocks,
getLastBlock,
addBlock,
getVersion,
mineBlock,
createHash,
replaceBlock,
}
window terminal 열어서 아래 명령어 실행 => 지금까지 오류가 있나 없나 쳌 (server on하고)
curl http://localhost:3000/blocks
curl -X POST -H "Content-Type:application/json" -d "{\"data\":[\"hello\"]}" http://localhost:3000/mineBlock
난이도 만들러 가좌
Difficulty
총 코인 개수정해놓고 난이도 조절할 수도. 블록 하나 만드는 시간 설정 가능
1. 시간 설정 - 마지막 블록의 시간으로 비교
- nextBlock에서 해야함 ! GenesisBlock함수에서 잘못 difficulty 수정했다가 오류남 (genesisBlock difficulty = 0 ;)
// 다음블럭의 Header와 Body를 만들어주는 함수 2번
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)
}
getDifficulty() 함수 생성 - 인자값 blocks 는 전체 블록배열을 가져온 것 => 배열임
2. 마지막 블록 가져오기
function getDifficulty(blocks){
const lastBlock = blocks[blocks.length-1];
}
3. 상수 설정
const fs = require('fs')
const merkle = require('merkle')
const CryptoJs = require('crypto-js')
const random = require('random')
const {hexToBinary} = require('./utils')
// 초 단위로 .. 블록 만들 때 기준점 만들기
const BLOCK_GENERATION_INTERVAL = 10; // 10초
// 블록의 개수에 따라 난이도 조정
// 블록이 10개째가 넘을 때마다 난이도 조정
const BLOCK_ADJUSTMENT_INTERVAL = 10;
4. 마지막 블록의 인덱스를 10으로 나눠서 딱떨어지면 (10의 배수일 때마다)
function getDifficulty(blocks){
const lastBlock = blocks[blocks.length-1];
// 마지막 블록의 index 값을 10으로 나눴을 때 0 이면 10의 배수이다.
// (제네시스 블록 제외)
if(lastBlock.header.index%BLOCK_ADJUSTMENT_INTERVAL===0
&& lastBlock.header.index !==0 ){
//난이도 조정하는 코드 필요함
}
return lastBlock.header.difficulty;
}
5. 난이도 조정하는 코드에 조정된 난이도를 return하는 함수 작성
function getDifficulty(blocks){
const lastBlock = blocks[blocks.length-1];
// 마지막 블록의 index 값을 10으로 나눴을 때 0 이면 10의 배수이다.
// (제네시스 블록 제외)
if(lastBlock.header.index%BLOCK_ADJUSTMENT_INTERVAL===0
&& lastBlock.header.index !==0 ){
//난이도 조정하는 코드 필요함
return getAdjustedDifficulty(lastBlock, blocks);
}
return lastBlock.header.difficulty;
}
6. getAdjustedDifficulty 함수 생성
function getAdjustedDifficulty(lastBlock, blocks){
// blocks 10개 단위로 끊는다. (게시판의paging처럼!)
// 난이도가 증가되기 전 값
// 10개의 이전 블록의 난이도를 가져와서 증가시키기
// 바로 이전 블록의 난이도를 가져오는 것보다 정확
const prevAdjustmentBlock = blocks[blocks.length-BLOCK_ADJUSTMENT_INTERVAL];
// ex. 총 배열이 20개다.-> 20 - 10 = 10
// blocks[10] 번째의 블록을 가리키게됨
// 시간 작업
// 10개 블록이 만들어지는 시간 차이 얼마나 나는가 구할 수 있음.
const timeToken = lastBlock.header.time-prevAdjustmentBlock.header.time;
}
완성
function getAdjustedDifficulty(lastBlock, blocks){
// blocks 10개 단위로 끊는다. (게시판의paging처럼!)
// 난이도가 증가되기 전 값
// 10개의 이전 블록의 난이도를 가져와서 증가시키기
// 바로 이전 블록의 난이도를 가져오는 것보다 정확
const prevAdjustmentBlock = blocks[blocks.length-BLOCK_ADJUSTMENT_INTERVAL];
// ex. 총 배열이 20개다.-> 20 - 10 = 10
// blocks[10] 번째의 블록을 가리키게됨
// 시간 작업
// 10개 블록이 만들어지는 시간 차이 얼마나 나는가 구할 수 있음.
const timeToken = lastBlock.header.time-prevAdjustmentBlock.header.time;
// 예상한 시간
const timeExpected = BLOCK_ADJUSTMENT_INTERVAL * BLOCK_GENERATION_INTERVAL;
//예상 시간보다 적게 걸렸다면 (너무 빨리만들면 == 문제가 너무 쉽게 풀리면)
// 내가 예상한 시간보다 반만큼 더 빠르면
if(timeToken < timeExpected/2){
return prevAdjustmentBlock.header.difficulty +1;
}else if(timeToken>timeExpected*2){
return prevAdjustmentBlock.header.difficulty -1;
}else{
return prevAdjustmentBlock.header.difficulty;
}
}
=> server on + 쳌쳌
질문
- 초를 100 으로 해도 되는지!? *1000을 안해도 되는지
=> 처음에 time 설정했을 때 애초에 /1000으로 나눠서 1= 1초가 됨
findBlock의 hash 를 console.log찍어보기
function findBlock(version, index, previousHash, time, merkleRoot, difficulty) {
let nonce = 0;
while (1) {
// 조건 : 우리가 만들 header의 hash값의 앞자리 0이 몇개인가
let hash = createHeaderHash(version, index, previousHash, time, merkleRoot, difficulty, nonce);
// 내가 가지고있는 hash에서 그 값을 2진수로 바꾸고
// 2진수에서 바꾼 값 중에서 첫글자가 0000인가~
console.log('findBlock의 hash이다.=', hash)
if (hashMatchDifficulty(hash,difficulty)) {
return new BlockHeader(version, index, previousHash, time, merkleRoot, difficulty, nonce)
}
nonce++;
}
}
-> 쭊 명령어 실행해보고 난이도 올라가는거 봐봐
(우리의 컴퓨터로는 난이도가 엄청 높으면 과부하......)
교수님이 실행해본 난이도 16
35만번 실행함 (5분정도 걸림)
다음시간 : wallet 지갑 / 인증시스템 만들어보기
'블록체인 기반 핀테크 및 응용 SW개발자 양성과정 일기' 카테고리의 다른 글
[블록체인 이더리움] ERC-20 & ERC-721 / dApp / 스마트 컨트랙트란? (0) | 2021.09.09 |
---|---|
[123일차] 블록체인 공개키 비밀키 만들기 (0) | 2021.09.09 |
[121일차] WebSocket ws로 Server 구현 (0) | 2021.09.07 |
[120일차] 블록체인 ws WebSocket (0) | 2021.09.06 |
[119일차 복습] 블록체인 네트워크 웹소켓 http ws 웹서버 구축 (0) | 2021.09.06 |