#1에서는 누구나 블록추가할 수 있었지만 이제는 계산을 푸는 작업을 해야 블록추가할 수 있게할거야. 이 계산 푸는 거를 채굴이라고 함. 또한 퍼즐의 난이도를 통해 블록 조절할거야. 좀 있다가는 트랜잭션(거래내역)에 대해서도 다룰거야. 이걸 통해서 보상받고 거래주고받고 하는거겠지
블록에 추가할 두 가지 속성 (-> 작업증명 퍼즐(블록의 해쉬값 찾기)을 풀기 위한 값)
difficulty 난이도 (=유효한 블록의 해쉬값은 얼마나 많은 0으로 시작해야하는가, 작은값찾기임)
해쉬값을 2진수로 표현한 다음 시작부분0의 갯수세어보는 것.
난이도 difficulty 가 4이면 임계값인 nonce가 1,2,3 올라가면서 해쉬값을 찾는다. 해쉬값계산 함수를 보면
난이도가 4일 때 hash:08f3... 이거를 binaryHash 2진수로 변환한거임 0: "0000", 8:"1000", f:"1111",3:"0011"
(숫자형태아니고 문자열 string형태라서 옆에 붙는다)
우리는 hexToBinary 함수를 안썼어. 그래서 nonce추가되는걸로 난이도 따라 해시값찾는걸로 나와. naive는 binaryHash기준으로 난이도 비교적 쉽게 통과되고 우리는 hash대로. 그니까 난이도 8일때 hash값 지금 0027..나오면 우린 안돼 더 돌려서 hash값 앞에 0이 최소한 8개여야 통과되는 거임.
아무튼 다시 naive코드로 와서
블록의 해시값이 난이도 difficulty를 만족하는지 확인하는 코드
const hashMatchesDifficulty=(hash, difficulty)=>{
해시값을 이진수화해.
난이도만큼 0을 반복해.
이진수화한 거가. 지정한문자로 시작하는지 확인. 0이 8개인 문자로.
}
난이도를 만족하는 해시값찾기 위해 같은 블록에서 여러번 계산해줘야한다. 이때 nonce값을 쓰는데 sha256라는 해시함수를 쓴다. 채굴이 바로 매번 다른 nonce값을 시도해보는 과정을 뜻한다.
블록구조, 최초의 블록 구조에 difficulty와 nonce추가
class Block{
난이도 추가
넌스값 추가
}
블록찾기
nonce값은 언제 증가시키는 findBlock 함수. 해시값을 찾을 때까지 반복문 돌릴거임.
const findBlock{
초기넌스값은 0
hash를 해시계산함수 안에 블록애들 넣어서 찾아.
만약에 난이도 확인하는 함수에 (해시랑, 난이도)보내서 통과되면
블록을 뱉어내(블록내용들을 담아서)
}
그렇게 블록이 채굴되면 네트워크통해서 발송(broadcast)되겠지
난이도(difficulty)를 어떻게 결정할건가
난이도값을 계산하는 로직필요해서 상수를 몇 개 정의하면서 만들었어. 하드코딩한거임. 물론 naive에서..ㅋㅋ
BLOCK_GENERATION_INTERVAL: 블록은 얼마나 자주 채굴되는가(Bitcoin의 경우 10분 간격이죠.)
DIFFICULTY_ADJUSTMENT_INTERVAL: 난이도difficulty는 얼마나 자주 조정되는가(Bitcoin은 2016블록마다 조정돼요.)
// in seconds
const BLOCK_GENERATION_INTERVAL: number = 10;
// in blocks
const DIFFICULTY_ADJUSTMENT_INTERVAL: number = 10;
신기한게 여기서는 그냥 임의로 지정한 숫자 10을 넣었는데 이게 어떻게 10초, 10블록으로 인식하나 싶었거든. 이게 같이 비교되는 애들따라서 인식을 하는거래. 코드를 보자
난이도가져오는함수
const getDifficulty=(블록체인)={
가장 마지막 블록= 블록체인.길이-1
만약에
마지막블록.index % BLOCK_GENERATION_INTERVAL(10블록으로 인식됨) 이 0이면 그리고 마지막블록.index가 0이 아니면
(우리는 Block에 헤더랑 바디 넣고, BlockHeader추가로 만들어서 거기에 버전이랑 인덱스 넣었음)
최초의 블록(제네시스블록)- 블록체인의 시작. 안의 값은 일일이 넣어주면 됨.
const genesisBlock
블록체인 싹 저장할 배열(javascript in memory 방식으로 저장. 컴끄면 사라짐)
const blockchain= [] //1장에서는 genesisBlock를 넣은 걸봐서 제네시스 블록 넣어서 보려나봄
필요한 함수 만들기
해쉬값계산함수
const calculateHash
다음블록만드는 함수(이전 해쉬 블록 알아야함)
const generateNextBlock
블록의 유효성 검사할 함수
그 조건임
이전 블록보다 인덱스값이 1 클 것.
previousHash값이 이전블록의 hash값일 것.
hash값이 유효한 값일 것. 다음의 코드로 이를 검사할 수 있어요.
const isValidNewBlock = (새블록, 이전블록) => {
1. 만약 이전블록인덱스가 새 블록인덱스보다 길이 더 길면
틀림 뱉어
2. 이전블록해시와 새블록이전해시가 같지 않으면
틀림뱉어
3. 새블록의 해시 계산한것이 새블록해시와 같지 않으면
틀림 뱉어
다 통과하면 맞음 뱉어
}
참고로 나 저기 들어가는 ,"새블록, 이전블록" 이렇게 써놓은 거, 파라미터(매개변수parameter)가 이름 내마음대로 지어줘도 되는건지 몰랐음.. 그래서 req, res도 막 거꾸로 써도 되더라고. 이전에 보내준 값(전달인자 argument)을 여기서 어떤 이름으로 쓸지 변수 정하고 이 함수 안에서 쓰는 것이었다!!!! 이전에 보내준 값이름이랑 이름이 달라도 상관없는 것!..ㅋㅋㅋ그동안 이름같은게 많길래 헷갈렸는데 이렇게 유동적이었다뉘
블록의 구조 검사하는 함수
const isValidBlockStructure {
블록 인덱스 타입은 숫자
블록 해시 타입은 문자열string(왜 끈, 줄이냐면 한글자씩연달아 메모리에 저장되는게 끈같아서..)
노드, 참여자용 서버로 만든 6001, 6002,6003은 블록 생성해서 중앙서버가 검증자해주는 건가?
잉 그러면 탈중앙화가 아닌데? 혼돈;;;
내가 이해를 잘못하고 있었음
"먼저 블록 채굴에 성공한 채굴자는 해당 블록을 주변에 전파하게 된다. 중요한 사실은, 이때 전파하는 블록은 채굴에 성공했다고 확신할 수 없는 블록이며 검증을 거쳐 블록체인에 연결되어야 한다. 거래는 노드들에게 전파되며 검증의 과정을 거치듯, 블록 또한 검증을 거치게 되며 검증의 조건은 다음과 같다....."
우리 p2p하려고 웹소켓 써서 주고받는 통신하잖아
그 전에는 기본 포트는 컴퓨터 내에서 물리적으로 연결되어있으니까 접속이 가능한거고
웹소켓으로 내 컴퓨터를 3개의 컴퓨터로 분리하다고 치고 6001, 6002, 6003에 하나씩 컴퓨터가 있고
실제로는 하나니까 물리적으로 3001에 연결될 수 있는거라 서버는 3001만 열어도 될거같기도하고..
암튼 결론은!
내가 연 3001은 그냥 이 모든게 보이는 용도로만 쓰이는 보여주기용 서버이고
실제로 블록 생성, 검증은
노드들이 하는거임
노드들의 서버 6001, 6002, 6003
그니까 얘네끼리 통신하는 p2p서버로 하는거지.
처음에 시작했을 때
제네시스 블록을 모든 노드들이 갖고 시작하고
그 뒤에 줄줄이 블록을 생성하는 거임
이제 검증과정을 거쳐서 서로블록을 비교하고
황금블록 발견한 애꺼가 당첨되면 원본 블록체인에 연결되고
나머지는 허탕치고 다시 다음블록 만드는 거임
그 checkedValid 코드부분은
//블록구조 유효한지 //현재 블록의 인덱스가 이전 블록의 인덱스보다 1만큼 큰지 //이전블록의 해시값과 현재 블록의 이전해시가 같은지 //데이터 필드로부터 계산한 머클루트와 블록헤더의 머클루트가 동일한지 //이 조건 다 맞으면 올바른 구조체이다
mnt는 mount의 약자 뜻을 가진 경로. 리눅스 프로그램에 기기의 탈부착으로 임시로 연결된 경로가 생기면 해당 경로에서 확인할 수가 있습니다. 현재 프로젝트의 AWS에는 연결된 외부 장치나 시스템이 없으므로 아무런 경로가 나오지 않습니다. 하지만 시스템의 용량을 확장할 때나 WSL에서 윈도우 시스템을 인식할 때 해당 경로로 인식할 수가 있습니다.
....이전과 같음
class BlockHeader{
constructor(version,index, previousHash, timestamp, merkleRoot, bit, nonce){
this.version = version
//index 값 넣기
this.index = index
this.previousHash = previousHash
this.timetamp = timestamp
this.merkleRoot = merkleRoot
this.bit = bit
this.nonce = nonce
}
}
//버전계산하는 함수
function getVersion(){
...
}
function createGenesisBlock(){
const version = getVersion()
//index 값 넣기
const index = 0
const previousHash = '0'.repeat(64)
const timestamp = parseInt(Date.now()/1000)
const body = ['hello block']
const tree = merkle('sha256').sync(body)
const merkleRoot = tree.root() || '0'.repeat(64)
const bit = 0
const nonce = 0
...
//헤더에 대입
const header = new BlockHeader(version, index, previousHash, timestamp, merkleRoot, bit,nonce)
return new Block(header, body)
}
//값넣어서 블록생성
const block = createGenesisBlock()
console.log(block)
1) index 값 넣어서 이후에 만들 블록체인 순서를 확인할 수 있게 한다.
(아까 index를 선언만 하고 BlockHeader constructor 구조에 안넣고 헤더에 대입에도 안넣어서 안됐었음
게다가 뒤에 선언하는거랑 넣어준 순서다르면 안되더라...)
2) 블록체인 불러오기
//여러개 들어갈 수 있는 배열로 Blocks를 만들어줌
let Blocks = [createGenesisBlock()]
//현재 있는 블록들을 다 리턴해주는 함수
function getBlocks(){
return Blocks
}
function getLastBlock(){
//길이 1이니까 1-1 =0 즉 첫번째배열 불러와
return Blocks[Blocks.length - 1]
}
블록체인의 블록들을 넣어줄 Blocks배열을 만들고 첫번째 블록을 생성하는 함수인 createGenesisBlock함수를 해당 배열에 미리 넣어둔다.
3)블록해시값 구하기
//블록해시 값 구하기
//data에는 블록이 들어오는거임, 이블록을 가지고 해시값을 만들어내는 것임
function createHash (data){
const {version,previousHash,timestamp, merkleRoot, bit, nonce}= data.header
const blockString = version +previousHash +timestamp+ merkleRoot+ bit+ nonce
const hash = crypto.js.SHA256(blockString).toString()
return hash
}
블록헤더 안에 든 정보들 (version 등)의 합산 정보를 구한후 SHA256으로 변환하면 블록의 블록해시값을 구할 수 있다.
crypto.js 쓰려면
const cryptojs =require('crypto-js')
이거 추가해야될거야
node 스크립트이름
다음블록 생성하기
//다음블록 생성하기
function nextBlock(bodyData) {
const prevBlock = getLastBlock()
}
이전 블록의 정보(가장 최신 블록 정보)를 getLastBlock함수를 이용해서 가져오고 prevBlock에 넣어둔다.
//다음블록 생성하기
function nextBlock(bodyData) {
const prevBlock = getLastBlock()
const version = getVersion()
//다음순서니까 하나 추가됨
const index = prevBlock.header.index +1
//createHash함수에 이전블록 정보를 넣어 블록해시값을 구해넣는다.
const previousHash = createHash(prevBlock)
const timestamp = parseInt(Date.now()/1000)
//블록body부분에 저장될 트랜잭션(거래정보)인 bodyData를
//merkle몯ㄹ을 사용하여 트랜잭션들의 해시트리를 만들어 tree에 넣어
const tree = merkle('sha256').sync(bodyData)
//여기서 최종적으로 구해진 해시값인 머클루트 해시값을 merkleRoot변수에 넣어
const merkleRoot = tree.root() || '0'.repeat(64)
const bit = 0
const nonce = 0
const header = new BlockHeader(index,version,previousHash,timestamp, merkleRoot, bit, nonce)
return new Block(header,bodyData)
}
머클루트 값을 통해 단일 블록내에 존재하는 트랜잭션의 무결성을 검증할 수 있고,
머클루트 값을 이용해 블록의 해시값을 생성했기 때문에 블록의 해시의 무결성도 함께 검증할 수 있다.
두번째 블록 생성하고 출력해보기
//다음블록 생성하기
function nextBlock(bodyData) {
const prevBlock = getLastBlock()
const version = getVersion()
//다음순서니까 하나 추가됨
const index = prevBlock.header.index +1
//createHash함수에 이전블록 정보를 넣어 블록해시값을 구해넣는다.
const previousHash = createHash(prevBlock)
const timestamp = parseInt(Date.now()/1000)
//블록body부분에 저장될 트랜잭션(거래정보)인 bodyData를
//merkle몯ㄹ을 사용하여 트랜잭션들의 해시트리를 만들어 tree에 넣어
const tree = merkle('sha256').sync(bodyData)
//여기서 최종적으로 구해진 해시값인 머클루트 해시값을 merkleRoot변수에 넣어
const merkleRoot = tree.root() || '0'.repeat(64)
const bit = 0
const nonce = 0
const header = new BlockHeader(version,index,previousHash,timestamp, merkleRoot, bit, nonce)
return new Block(header,bodyData)
}
//다음블록생성 출력하기
const block1 = nextBlock(["tranjaction1"])
console.log(block1)