정보실

웹학교

정보실

javascript Node.js에서 블록 체인 작성

본문

블록 체인은 요즘 컴퓨터 과학에서 매우 인기 있는 주제입니다. 2008 년에 Bitcoin cryptocurrency 덕분에 유명해졌습니다 .Bitcoin cryptocurrency는 블록 체인을 사용하여 모든 거래를 공개 분산 원장에 기록합니다.


https://www.jsmonday.dev/articles/34/writing-a-blockchain-in-node-js 


하지만 ... 블록 체인에 대해 얼마나 알고 있습니까?


우리가 블록 체인의 작동 방식을 이해하고 싶다면 어떻게 해야 합니까? 어떻게 시작해야 합니까?


기초부터 시작합시다!


블록 체인이란 무엇입니까? 


블록 체인은 말 그대로 블록 체인으로, 블록은 공용 데이터베이스 인 체인 안에 저장된 정보입니다.


JavaScript에서는 블록을 객체로 생각할 수 있습니다.


{ timestamp: 1568468720410, data: "I am a block", previousHash: "2510c39011c5be704182423e3a695e91", hash: "363b122c528f54df4a0446b6bab05515" } 


이제 위의 블록을 분석해 봅시다 :

  • timestamp : 블록 생성의 타임 스탬프
  • data : 블록 안에 포함 된 데이터. 객체, 문자열, 숫자 등 무엇이든 가능합니다. 우리는 일반적으로 블록의 이 부분 안에 트랜잭션 데이터를 저장합니다.
  • hash : 현재 블록 해시. 블록 안에 포함 된 데이터를 나타냅니다. 이 블록 안의 내용을 편집하면 해시가 변경됩니다.
  • previousHash : 이전 블록을 나타내는 해시.


잠깐만 기다려요! 이전 블록? 예! 실제로 블록 체인의 주요 특징은 체인의 모든 블록이 이전 블록을 나타내는 데이터를 포함해야 한다는 것입니다. 이렇게 하면 하나의 블록을 수정하려면 전체 체인을 다시 계산 해야 합니다. 나중에는 나중에 볼 수 있지만 실제로 불가능할 수도 있습니다.

이런 이유로 블록 체인은 불변의 구조라고 말할 수 있습니다.


최소한의 블록 체인 구축 


그렇다면 블록 체인을 어떻게 작성하기 시작합니까? 

우선, 블록 체인의 가장 중요한 기능인 블록의 해시를 계산하는 기능을 정의 해 봅시다.


const SHA256 = require("crypto-js/sha256"); function calculateHash({previousHash, timestamp, data, nonce = 1}) { return SHA256(previousHash + timestamp + JSON.stringify(data) + nonce).toString(); } 


보시다시피, 블록의 해시를 계산하려면 previousHash, timestamp, data 및 nonce의 네 가지 매개 변수가 필요합니다. 처음 세 가지 매개 변수가 무엇인지 보았지만 왜 nonce가 필요한가요?

보안상의 이유로.

악의적인 개발자가 블록의 내용을 변경한 다음 전체 체인을 다시 계산하여 유효하게 만들기를 원하지 않습니다. 따라서 해시의 형식을 지정하는 방법에 대한 제약 조건을 정의해야 합니다.


예를 들어 다음과 같은 블록이 있다고 가정 해 보겠습니다.


{ data: { sender: "x3041d34134g22d", receiver: "x89sj8ak2l9al18", amount: 0.0012, currency: "BTC" }, timestamp: 1568481293771, previousHash: "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", } 


해시는 몇 밀리 초 안에 계산되며 다음과 같습니다.


6b15398dcfeac338ac05bc61e37f575819985f897f0a56e329b574b52a3ed4dd 


이게 뭐가 문제야?


초당 수천 번의 해싱 계산을 수행 할 수 있는 슈퍼 컴퓨터가 있다고 상상해보십시오. 

전체 체인을 다시 계산하는 데 얼마나 걸립니까? 제약 조건을 설정하면 인생을 더 힘들게 만들 수 있습니다 

예를 들어, 우리는 4 개의 0과 4 개의 9로 시작하는 경우에만 해시가 유효하다고 결정할 수 있습니다.


00009999cfeac338ac05bc61e37f575819985f897f0a56e329b574b52a3ed4dd 


그러나 해시는 블록의 내용에 달려 있습니다 ... 어떻게 이 제약을 존중할 수 있습니까? nonce가 온다!


nonce는 해시 계산 중에 삽입 된 임의의 값으로 형식이 잘 지정된 해시를 얻는 것이 더 쉽습니다. 

우리 해시는 부과 된 규칙을 존중하지 않습니까? 다른 nonce로 다시 계산해 봅시다.


이제 몇 가지 다른 제약 조건을 테스트하고 해시를 계산하는 데 시간이 얼마나 걸리는지 살펴 보겠습니다.

  • 해시는 두 개의 0으로 시작해야 합니다. 211ms
  • 해시는 세 개의 0으로 시작해야 합니다. 297ms
  • 해시는 5 개의 0으로 시작해야 합니다 : 5966ms
  • 해시는 f5로 시작해야 합니다 : 174ms
  • 해시는 0990으로 끝나야 합니다 : 196ms
  • 해시는 f918로 끝나야 합니다 : 4436ms

시간이 많이 걸리는 규칙을 정의한다고 상상해보십시오. 

단일 블록을 계산하는 데 약 4 시간이 걸리는 경우 전체 체인을 계산 (또는 "광산")하는 데 얼마나 걸립니까?


기원 블록 


블록 체인의 첫 번째 블록을 “창세기 블록”이라고 합니다. "이전 해시"값을 가질 수 없으므로 이를 생성하는 함수를 작성하는 것이 매우 간단합니다.

function generateGenesisBlock() { const block = { timestamp: + new Date(), data: "Genesis Block", previousHash: "0", }; return { ...block, hash: calculateHash(block) } } 


이제 generateGenesisBlock 함수를 실행 해 봅시다. 어떻게 되는지 봅시다 :


{ timestamp: 1568533984830, data: 'Genesis Block', previousHash: '0', hash: '7c8b7226901c9ccfb1e1b0301685f6001431e9fd7dbb408fd7735f95dd314627' } 


새로운 블록 채굴 


이제 블록 체인을 위한 새로운 블록을 계산하는 함수가 필요합니다. 그러나 먼저 해시 제약 조건을 정의해야 합니다.


function checkDifficulty(difficulty, hash) { return hash.substr(0, difficulty) === "0".repeat(difficulty) } 


위의 함수 (checkDifficulty)에서 어려움과 해시라는 두 가지 인수를 전달할 수 있습니다. 우리는 일을 매우 단순하게 유지하기를 원하므로 (해당 구현을 위해) 해시는 4 개의 0으로 시작해야 한다고 결정합니다.


전달 된 해시가 제약 조건을 준수하면 이 함수는 true를 반환합니다. 그렇지 않으면 false를 반환하므로 nonce를 변경하여 (전과 같이) 다시 계산해야 합니다.


알다시피 보다 테스트 가능하고 모듈화 된 코드베이스를 보장하기 위해 기능 패러다임을 채택하고 있습니다. 

따라서 다음 nonce를 계산하려면 새로운 nonce를 포함하여 블록을 가져와 수정 된 버전을 반환하는 함수가 필요합니다. 부작용을 피하고 싶기 때문에 nonce 값을 직접 편집하지 않습니다.


function nextNonce(block) { return updateHash({ ...block, nonce: block.nonce + 1 }) } 


보시다시피 updateHash의 결과를 반환합니다. updateHash는 기본적으로 블록을 가져 와서 새 해시로 새 버전을 반환합니다.


function updateHash(block) { return { ...block, hash: calculateHash(block) } } 


마지막으로, 부과 된 제약 조건을 준수 할 때까지 블록의 해시를 계산하는 재귀 함수가 필요합니다. 

그러나 JavaScript에서 재귀를 안전하게 만드는 방법은 무엇입니까? 

고맙게도 JavaScript의 재귀와 메모리를 안전하게 만드는 방법에 대한 기사를 썼습니다! 트램폴린을 구현해 봅시다 (자세한 내용은 여기 참조).


function trampoline(func) { let result = func.apply(func, ...arguments); while(result && typeof(result) === "function") { result = result(); } return result; } 


이것이 코드베이스의 불순한 부분이지만 메모리 관련 오류를 방지하기 위해 반드시 필요합니다.


이제 모두 함께합시다 :


function mineBlock(difficulty, block) { function mine(block) { const newBlock = nextNonce(block); return checkDifficulty(difficulty, newBlock.hash) ? newBlock : () => mine(nextNonce(block)); } return trampoline(mine(nextNonce(block))); } 



위의 코드를 분석해 봅시다 :

  • 어려움을 겪고 차단하는 새로운 함수를 인수로 정의합니다. 난제는 해시로 시작해야 하는 0의 수를 나타내는 숫자일 뿐입니다. block은 우리가 채굴 하려는 블록일 뿐입니다.
  • 우리는 블록을 인수로 취하는 함수 광산을 정의합니다.
  • 그 함수 안에서 새로운 nonce를 가진 새로운 블록을 만듭니다.
  • 그런 다음 해시가 규칙을 준수하는지 확인해야 합니다. 그렇다면 newBlock 블록을 결과로 반환하겠습니다. 그렇지 않으면, nonce를 변경하고 다시 채굴해야 합니다.
  • 이제 광산 기능을 갖추었으므로 트램폴린 안에 넣습니다. 블록에 대한 올바른 해시를 얻을 때까지 함수를 재귀 적으로 실행합니다.

이제 마지막 함수가 하나 필요합니다 :


function addBlock(chain, data) { const { hash: previousHash } = chain[chain.length - 1]; const block = { timestamp: + new Date(), data, previousHash, nonce: 0 } const newBlock = mineBlock(4, block); return chain.concat(newBlock); } 


보시다시피 addBlock 함수는 전체 체인을 첫 번째 인수로 사용하고 새 블록 데이터를 두 번째 인수로 사용합니다. 

그런 다음 새 블록을 마이닝하고 새로 만든 블록이 포함 된 새 체인을 반환합니다.


체인 검증 


지금까지 많은 일을 했습니다! 이제 새로운 블록 삽입 후 전체 체인을 검증하는 함수를 구현해야 합니다.


function validateChain(chain) { function tce(chain, index) { if (index === 0) return true; const { hash, ...currentBlockWithoutHash } = chain[index]; const currentBlock = chain[index]; const previousBlock = chain[index - 1]; const isValidHash = (hash === calculateHash(currentBlockWithoutHash)); const isPreviousHashValid = (currentBlock.previousHash === previousBlock.hash); const isValidChain = (isValidHash && isPreviousHashValid); if (!isValidChain) return false; else return tce(chain, index -1); } return tce(chain, chain.length - 1) } 


이 함수는 설명이 필요 없습니다.

  • 우리는 전체 체인을 인수로 취하는 새로운 함수를 만듭니다.
  • 체인과 블록 인덱스를 인수로 취하는 tce (꼬리 호출 제거를 의미)라는 새로운 함수를 정의합니다.
  • 체인의 첫 번째 인덱스 (Genesis Block)를 살펴보면 true를 반환합니다.
  • 그렇지 않으면 몇 가지 조건을 확인해야 합니다
    현재 블록의 해시가 유효합니까?
    이전 블록의 해시가 유효합니까?
    체인이 유효합니까 (지금까지)?

그것은 매우 간단하지만 매우 강력합니다!


모든 것을 하나로 모으기 


이제 블록 체인을 테스트 할 준비가 되었습니다! 

우선 초기화 해 봅시다 :


let chain = [generateGenesisBlock()]; 


이제 새로운 블록을 만들어 봅시다 :


const newBlockData = { sender: "ks829fh28192j28d9dk9", receiver: "ads8d91w29jsm2822910", amount: 0.0023, currency: "BTC" } 


이제 이 새로운 블록을 채굴 할 준비가 되었습니다!


const newChain = addBlock(chain, newBlockData); 


새로 생성 된 체인을 기록해 봅시다 :


console.log(newChain); 


[ { timestamp: 1568556333240, data: 'Genesis Block', previousHash: '0', hash: 'd7fdf427ec6c60803204004b5f141570996dd8ea33d9f56b32a76d89ada83f5b' }, { timestamp: 1568556333242, data: { sender: 'ks829fh28192j28d9dk9', receiver: 'ads8d91w29jsm2822910', amount: 0.0023, currency: 'BTC' }, previousHash: 'd7fdf427ec6c60803204004b5f141570996dd8ea33d9f56b32a76d89ada83f5b', nonce: 25569, hash: '00004aedfcae1d45e14bc044e9559a6372bb4a6edb544e806878df63f08127e6' } ]; 


이제 블록 체인에 원하는 만큼의 블록을 추가 할 수 있습니다!


몇 가지 고려 사항 


우리는 부작용 없이 전혀 기능적인 블록 체인을 만들었습니다. 테스트를 작성하고 100 % 적용 범위를 쉽게 얻을 수 있으며, 수많은 데이터를 처리 할 수 있는 응용 프로그램을 작성할 때 명심해야 합니다.


순수한 함수 대신 클래스를 사용하고 재귀 대신 루프를 사용하면 더 쉽고 성능이 향상 될 수 있습니다 .

하지만 성능에 너무 신경 쓰지 않습니다 (즉, 우리는 마이닝 블록에 며칠을 보내고 있습니다. 중요한가요?) 

우리는 너무 단순하지 않기를 원합니다. 함수형 프로그래밍을 통해 재사용 가능한 함수와 훌륭한 테스트를 통해보다 견고한 코드를 작성할 수 있습니다.


다음 단계 


우리는 블록 체인의 작동 방식과 구축 방법에 대해 흠집을 냈으며 여전히 해야 할 일이 많이 있습니다!

  • 해싱 제약 조건을 개선하십시오. 새로운 블록을 채굴 하기 어려운 규칙을 찾으십시오.
  • Tail 콜 재귀는 Node.js 및 브라우저에서 구현되지 않습니다 (EcmaScript 언어 사양에 설명되어 있음). validateChain 내부의 tce 함수를 트램폴린으로 변환하십시오!
  • 우리는 블록 체인 내부에서 변경 가능한 값을 원하지 않는다고 말했습니다. 그렇다면 기존 체인을 업데이트하는 새로운 블록 생성을 어떻게 처리 할 수 ​​있습니까?

“광부”는 어떻게 돈을 얻습니까? 


보시다시피, 새로운 블록을 채굴 하려면 엄청난 시간이 필요하며, 이는 전기 에너지 비용으로 변환 될 수 있습니다. 따라서 새로운 블록을 채굴 하는 사람들은 채굴 후에 "보상"을 받을 수 있습니다. 돈이 나오는 곳




  • 트위터로 보내기
  • 페이스북으로 보내기
  • 구글플러스로 보내기
  • 카카오톡으로 보내기

페이지 정보

조회 31회 ]  작성일19-09-25 20:22

웹학교