본문 바로가기

Front-End

NomardCoders - Typescript로 블록체인 만들기

VSCode TypeScript 준비

1. tsconfig.json 생성

(파일을 쓸 때에는 주석의 한글 설명을 삭제해야 정상 실행됨)

{
    "compilerOptions": { //컴파일 옵션 설정
        "module": "commonjs",
        "target": "es2015",
        "sourceMap": true 
    },
    "include": ["index.ts"], //컴파일과정에서 포함할 파일
    "exclude": ["node_modules"], //컴파일과정에서 제외
    
}

2. index.ts 생성

console.log("test");

3. 터미널 실행

yarn init 실행 시 package.json 파일이 생성됨

yarn init

4. 생성된 package.json 파일 수정

TypeScript tsc는 컴파일 명령어로 TypeScript 파일(.ts)을 자바스크립트 파일로 트랜스파일링함.

실행 전에 반드시 실행해야하는 명령어이므로 자동으로 실행되기 위해 package.json에 정의해줌.

{
  "name": "typescript_blockchain",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/wodbow12/typescript_blockchain.git",
  "author": "dongwon <wodbow12@gmail.com>",
  "license": "MIT",
  "scripts": {
  	"prestart": "tsc", //타입스크립트 실행 전에 tsc 명령어로 js로 컴파일 시켜줌
	"start": "node index.js" //js화 된 파일을 실행
    
  }
}

5. 타입스크립트 실행

yarn start

 


1. 기본 함수 예제

const name = "dongwon",
    age = 34,
    gender = "Male";

const sayHi = (name, age, gender?) => { //물음표를 삽입하면 파라미터 정의가 되지 않아도 실행
    console.log(`Hello ${name} you are ${age}, you are a ${gender}`);
}

sayHi(name, age); //gender 파라미터를 빼도 정상실행됨
export {};​
const name = "dongwon",
    age = 34,
    gender = "Male";

const sayHi = (name, age, gender) => { 
    console.log(`Hello ${name} you are ${age}, you are a ${gender}`);
}

sayHi(name, age, gender); //파라미터값이 하나라도 빠지면 에러남
export {};

2. Type in Typescript

파라미터 형식을 지정하지 않을 때 마우스를 오버하면 아래 그림과 같이  any=object, array, boolean 상관없이 값을 전달할 수 있음

함수를 봤을 때 어떤 형식의 파라미터 값이 필요하고 어떤 형식으로 리턴받는지 예상가능한 코드가 유지보수 측면으로도 좋으므로 typed 하게 바꿔줌.

const sayHi = (name:string, age:number, gender:string):string => {
    return(`Hello ${name} you are ${age}, you are a ${gender}`);
}

var sayHiPrint = sayHi("dongwon", 34, "Male");
console.log(sayHiPrint);
export {};

yarn start 이제 그만! 

1. tsc-watch 라이브러리 설치

ts파일을 수정할 때마다 yarn start를 실행하기엔 번거로운 부분이 있으므로 tsc-watch 라이브러리를 설치하여

ts파일이 수정되는 즉시 컴파일 업데이트 되도록 함.

yarn add tsc-watch --dev

 

yarn add typescript

2. 설정 파일 옵션 변경

1) pakage.json : –onSuccess 옵션으로 ts compile(tsc)이 성공했을 시에만 동작하게 설정

  "scripts": {
    "start": "tsc-watch --onSuccess \" node dist/index.js \" "
  },

2) tsconfig.json 

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "ES2015",
        "sourceMap": true,
        "outDir": "dist"
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules"]
}

3. 폴더, 파일 정리

노마드코더 영상으로 진행을 하다보면 root 경로에 ts 파일, 설정파일, 컴파일된 파일등 모두 모여있게 되는데 이를 정리하고나서,

정리된 폴더경로로 tsc-watch를 위한 설정파일 수정을 진행.

 

/dist 폴더 생성 - 컴파일된 파일

/src  폴더 생성 - ts 파일 (index.ts 파일 이동)

* index.js, index.js.map 은 dist 폴더에 생성되게 되므로 기존에 생성된 파일은 삭제해줌

 

4. 실행

yarn start

실행해주면 index.ts를 수정 할 때마다 내용이 업데이트 됨.


Interface in Typescript

파라미터 값들을 Object로 넘기고 싶을 때는 Interface 를 사용함.

interface Human {
    name : string;
    age : number;
    gender : string;
}

const person = {
    name : "dongwon",
    age : 34,
    gender : "male"
}

const sayHi = (person:Human):string => {
    return(`Hello ${person.name} you are ${person.age}, you are a ${person.gender}`);
}

var sayHiPrint = sayHi(person);
console.log(sayHiPrint);
export {};

 


Classes On Typescript part One

Interface 는 js로 컴파일 되지 않지만 가끔 인터페이스를 js에 넣고 싶을 때 class를 사용함.

js에서는 class의 속성에 대해 묘사하지 않지만 ts에서 class는 어떤 타입이어야하는지, 어떤 권한을 가져야하는지 상세하게 알려줘야함.

class Human {
    public name: string;
    public age: number;
    public gender:string;
    constructor (name:string, age:number, gender:string){
        this.name = name;
        this.age = age;
        this.gender=gender;
    }
}

const dongwon = new Human("dongwon", 34, "Male");

const sayHi = (person:Human):string => {
    return(`Hello ${person.name} you are ${person.age}, you are a ${person.gender}`);
}

var sayHiPrint = sayHi(dongwon);
console.log(sayHiPrint);
export {};

interface나 class는 상황에 따라 선택해서 쓰면 되는데, ts에서는 interface를 사용하기에 더 적합하고 react, express 등에서는 class를 사용해야함.

클래스 내의 권한은 public, privite, protected 중 사용 용도에 따라 데이터 권한을 지정해줌으로써 좀 더 안전한 코딩이 가능해짐.


Blockchain Creating a Block

ts에서는 let과 const를 주로 사용함

 

단순형의 경우 값의 변경이 있는 경우 let 사용.

상수형으로 사용하는 경우 const 사용.

블럭구조 만들기

class Block {
    public index:number;
    public hash:string;
    public previousHash:string;
    public data:string;
    public timestamp:number;
    constructor(
        index:number,
        hash:string,
        previousHash:string,
        data:string,
        timestamp:number
    ){
        this.index = index;
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
        this.timestamp = timestamp;
    }
}

const genesisBlock = new Block(0, "123123123", "", "Hello", 123456);
let blockchain: [Block] = [genesisBlock];
console.log(blockchain);

export{};

위 코드를 실행하면 아래처럼 출력됨.

typescript (ts)가 blockchain 변수에 Block만 담기는 지 검사 해줌.


Creating a Block part Two

crypto-js 라이브러리 

yarn add crypto-js

설치한 후 import * as Crypto from 'crypto-js'; 명령어로 임포트함.

 

HASH함수를 이용한 블록 생성

import * as CryptoJS from 'crypto-js';
class Block {
    public index:number;
    public hash:string;
    public previousHash:string;
    public data:string;
    public timestamp:number;

    static calculateBlockHash = (
        index:number, 
        previousHash:string,
        timestamp:number,
        data:string):string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();
    
    constructor(
        index:number,
        hash:string,
        previousHash:string,
        data:string,
        timestamp:number
    ){
        this.index = index;
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
        this.timestamp = timestamp;
    }
}

const genesisBlock = new Block(0, "123123123", "", "Hello", 123456);

let blockchain: [Block] = [genesisBlock];

//나중에 사용 할 함수 정의
const getBlockchain = () : Block[] => blockchain;

const getLatestBlock = () : Block => blockchain[blockchain.length -1];

const getNewTimeStamp = () : number => Math.round(new Date().getTime() / 1000);

console.log(blockchain);

export{};

calculateBlockHash 라는 함수를 만들어서 해쉬값을 구하는데 class 내에서 일반적인 메서드 선언 문법을 사용하면 해당 메서드는 반드시 블록을 생성했을 때에만 사용할 수 있으므로 함수를 static 으로 선언함.


Creating a Block part Three

import * as CryptoJS from 'crypto-js'; 

class Block {
    public index: number;
    public hash: string;
    public previousHash: string;
    public data: string;
    public timestamp: number;

    static calculateBlockHash = (
        index: number, 
        previousHash: string, 
        data: string, 
        timestamp: number
    ): string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

    constructor(index: number, hash: string, previousHash: string, data: string, timestamp: number) {
        this.index = index;
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
        this.timestamp = timestamp;
    }
}

const genesisBlock: Block = new Block(0, "123123123", "", "hello", 123456);

let blockchain: Block[] = [genesisBlock];

const getBlockchain = () : Block[] => blockchain;

const getLatestBlock = () : Block => blockchain[blockchain.length - 1];

const getNewTimesStamp = () : number => Math.round(new Date().getTime() / 1000);

const createNewBlock = (data: string) : Block => {
    const previousBlock: Block = getLatestBlock();
    const newIndex: number = previousBlock.index + 1;
    const nextTimestamp: number = getNewTimesStamp();
    const nextHash: string = Block.calculateBlockHash(newIndex, previousBlock.hash, data, nextTimestamp);
    const newBlock: Block = new Block(newIndex, nextHash, previousBlock.hash, data, nextTimestamp);
    return newBlock;
}

console.log(createNewBlock("hello"));
console.log(createNewBlock("bye bye"));

export {};

위 코드를 실행하면 아래와 같이 출력이 되는데 Index가 둘 다 1로 출력됨.

아직 블록체인에 push 하지 않았기 때문임.


Validating Block Structure 

블록이 isValid라는 구조를 가지는지 확인

Ts가 많이 체크를 해주지만 해쉬가 정확한지 확인이 필요함

블록의 구조가 유효한지 검사함.

import * as CryptoJS from 'crypto-js'; 

class Block {

    static calculateBlockHash = (
        index: number, 
        previousHash: string, 
        data: string, 
        timestamp: number
    ): string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

    static validateStructure = (aBlock: Block): boolean => 
        typeof aBlock.index === "number" && 
        typeof aBlock.hash === "string" && 
        typeof aBlock.previousHash === "string" && 
        typeof aBlock.data ==="string" &&
        typeof aBlock.timestamp === "number";
    

    public index: number;
    public hash: string;
    public previousHash: string;
    public data: string;
    public timestamp: number;

    constructor(index: number, hash: string, previousHash: string, data: string, timestamp: number) {
        this.index = index;
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
        this.timestamp = timestamp;
    }
}

const genesisBlock: Block = new Block(0, "123123123", "", "hello", 123456);

let blockchain: Block[] = [genesisBlock];

const getBlockchain = () : Block[] => blockchain;

const getLatestBlock = () : Block => blockchain[blockchain.length - 1];

const getNewTimesStamp = () : number => Math.round(new Date().getTime() / 1000);

const createNewBlock = (data: string) : Block => {
    const previousBlock: Block = getLatestBlock();
    const newIndex: number = previousBlock.index + 1;
    const nextTimestamp: number = getNewTimesStamp();
    const nextHash: string = Block.calculateBlockHash(newIndex, previousBlock.hash, data, nextTimestamp);
    const newBlock: Block = new Block(newIndex, nextHash, previousBlock.hash, data, nextTimestamp);
    return newBlock;
}

const getHashforBlock = (aBlock: Block) : string => Block.calculateBlockHash(aBlock.index, aBlock.previousHash, aBlock.data, aBlock.timestamp);

const isBlockValid = (candidateBlock: Block, previousBlock: Block): boolean => {
    if (!Block.validateStructure(candidateBlock)) { //candidateBlock 이 유효하지 않으면 False
        return false;
    } else if (previousBlock.index + 1 !== candidateBlock.index) { //previousBlock의 인덱스+1랑 candidateBlock블록의 인덱스가 다르면 False
        return false;
    } else if (previousBlock.hash !== candidateBlock.previousHash) { //previousBlock의 해쉬와 candidateBlock블록의 previousHash가 다르면 False
        return false;
    } else if (getHashforBlock(candidateBlock) !== candidateBlock.hash) { //따로 해쉬를 계산해서 블록의 해쉬가 유효한지 체크
        return false;
    } else {
        return true;
    }
}

export {};

Validating Block Structure Part Two

블록을 블록체인에 추가

import * as CryptoJS from 'crypto-js'; 

class Block {

    static calculateBlockHash = (
        index: number, 
        previousHash: string, 
        data: string, 
        timestamp: number
    ): string => CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

    static validateStructure = (aBlock: Block): boolean => 
        typeof aBlock.index === "number" && 
        typeof aBlock.hash === "string" && 
        typeof aBlock.previousHash === "string" && 
        typeof aBlock.data ==="string" &&
        typeof aBlock.timestamp === "number";
    

    public index: number;
    public hash: string;
    public previousHash: string;
    public data: string;
    public timestamp: number;

    constructor(index: number, hash: string, previousHash: string, data: string, timestamp: number) {
        this.index = index;
        this.hash = hash;
        this.previousHash = previousHash;
        this.data = data;
        this.timestamp = timestamp;
    }
}

const genesisBlock: Block = new Block(0, "123123123", "", "hello", 123456);

let blockchain: Block[] = [genesisBlock];

const getBlockchain = () : Block[] => blockchain;

const getLatestBlock = () : Block => blockchain[blockchain.length - 1];

const getNewTimesStamp = () : number => Math.round(new Date().getTime() / 1000);

const createNewBlock = (data: string) : Block => {
    const previousBlock: Block = getLatestBlock();
    const newIndex: number = previousBlock.index + 1;
    const nextTimestamp: number = getNewTimesStamp();
    const nextHash: string = Block.calculateBlockHash(newIndex, previousBlock.hash, data, nextTimestamp);
    const newBlock: Block = new Block(newIndex, nextHash, previousBlock.hash, data, nextTimestamp);
    addBlock(newBlock); //새로 생성한 블록을 addBlock 함수를 사용하여 블록체인에 추가
    return newBlock;
}

const getHashforBlock = (aBlock: Block) : string => Block.calculateBlockHash(aBlock.index, aBlock.previousHash, aBlock.data, aBlock.timestamp);

const isBlockValid = (candidateBlock: Block, previousBlock: Block): boolean => {
    if (!Block.validateStructure(candidateBlock)) { //candidateBlock 이 유효하지 않으면 False
        return false;
    } else if (previousBlock.index + 1 !== candidateBlock.index) { //previousBlock의 인덱스+1랑 candidateBlock블록의 인덱스가 다르면 False
        return false;
    } else if (previousBlock.hash !== candidateBlock.previousHash) { //previousBlock의 해쉬와 candidateBlock블록의 previousHash가 다르면 False
        return false;
    } else if (getHashforBlock(candidateBlock) !== candidateBlock.hash) { //따로 해쉬를 계산해서 블록의 해쉬가 유효한지 체크
        return false;
    } else {
        return true;
    }
}

const addBlock = (candidateBlock: Block) : void => {
    if(isBlockValid(candidateBlock, getLatestBlock())) { 
        blockchain.push(candidateBlock);
    }
}

export {};

CreateNewBlock 에서 생성한 블록을 addBlock를 사용하여 유효성을 체크한 후 블록체인에 등록함.

실행

createNewBlock("second block");
createNewBlock("thrid block");
createNewBlock("fourth block");

console.log(blockchain);

위 코드로 실행을 해보면 블록 세개가 추가 된 것이 확인되며, 블록체인 구조인 이전의 해쉬값과 생성된 해쉬값들이 잘 들어가있어 정상적으로 수행되었다는 것을 알 수 있음.