All Articles

TypeScript로 블록체인 만들기(feat.니콜라스형)

image.png

본 포스팅은 노마드코더 TypeScript로 블록체인 만들기 강의를 보고 따라 친 글입니다.

TypeScript 간단 초기세팅

  1. yarn init 초기화 명령어를 통해 package.json 생성

image.png

  1. yarn global add typescript 글로벌로 깔아주었음
  2. tsconfig.json 생성
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "ES2015",
    "sourceMap": true
  },
  "include": ["index.ts"],
  "exclude": ["node_modules"]
}

이렇게 설정해주었음

  1. index.ts 생성
    console.log("hello") 라는 간단한 명령어 입력후 터미널에 tsc 명령어를 입력해보면 index.js와 index.js.map 파일이 생성된다! 근데 tsc 명령어 보다 yarn 을 쓰고 싶어서 package.json 수정고고
{
  "name": "typechain",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "node index.js",
    "prestart": "tsc"
  }
}

image.png

짜잔~ 이렇게 터미널 상에 콘솔이 찍힌다.

TypeScript : 타입쓰

간단한 예제로 콘솔을 찍어보자.

const name = "Doori",
  age = 28,
  gender = "female";

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

sayHi(name, age, gender);

export {};

제일 마지막줄에 export {}를 하지 않으면 에러 발생!

이렇게 했을때 터미널에 콘솔이 아주 잘 찍힌다.
하지만 sayHi라는 함수에서 인자를 하나 빼보면 어떨까?

image.png

정말 까다로운 녀석이다. 바로 에러를 뱉는다!
하지만 이렇게 깐깐한 만큼 나중에 디버깅할때 좀 더 수월해진다는 장점쓰~
인자가 세 개지만 꼭 두 개만 쓰고싶다? 그럼 방법이 또 있쥐

const name = "Doori",
  age = 28,
  gender = "female";

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

sayHi(name, age);

export {};

인자 뒤에 물음표를 붙이면 필수적인 인자가 아닌 선택적인 것!
yarn start를 해보면 대신 콘솔이 Hello Doori, you are 28, you are a undefined 이렇게 찍힌다 ㅋㅋ

자세한건 지난번 타입스크립트 정리 1탄 글을 참고해보자!
지난번 타입스크립트 정리글 링크

TSC watch

yarn add tsc-watch --dev 명령어로 tsc watch를 깔아주고… package.json을 다시 한번 수정!

// package.json
{
  "name": "typechain",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "tsc-watch --onSuccess \" node dist/index.js\" "
  },
  "devDependencies": {
    "tsc-watch": "^4.0.0"
  },
  "dependencies": {
    "crypto-js": "^3.1.9-1",
    "typescript": "^3.7.2"
  }
}

tsconfig.json도 수정

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

include쪽을 보면.. 모든 파일을 src 하에 관리하겠단 소리라서 내가 만든 index.ts 파일을 src 밑으로 넣어준다.

이렇게 설정을 해주면 모든 TypeScript는 src로 들어가고, 컴파일된 것들은 dist로 들어간다. yarn start 하면 에러가 안 나야 하는데

image.png

이런 에러가 뜬다???
yarn add typescript 명령어로 조진다.

image.png

yarn start를 다시 하면 잘 나온다!
저게 지금 watch모드가 실행중인건데.. 무언가 변화가 생기면 인제 dist에 기록이 된다

Interface

image.png

위와 같이 입력했을때 에러 발생..! 그래서 여기에 인터페이스를 적용할거다

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

const person = {
  name: "Doori",
  age: 28,
  gender: "female"
};

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

console.log(sayHi(person));

export {};

Human이라는 인터페이스를 객체형식으로 만들었고, sayHi라는 함수에 person의 타입을 Human으로 정의해주었다.
그랬더니 에러가 발생하지 않았다! 오 씐기씐기
주의할 점은 객체 쓰는것처럼 템플릿 리터럴 안의 변수명을 dot notation을 이용해 써야한다.

인터페이스라는 개념은 JS에 없고 TS에만 있는거라는데..
나중에 한.. ES13쯤 가면 자바스크립트에도 분명 생긴다 ㅋㅋㅋ

Class in TypeScript

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 chicken = new Human("NeNe", 10, "rooster");

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

console.log(sayHi(chicken));

export {};

인터페이스와 비슷하지만 class를 이용하는 방법도 있다.
이걸 왜쓰냐? 자바스크립트가 인터페이스는 인식하지 못하기 때문! 또한 TS를 쓰는 측면에서 좀더 안전한 방법이라고 한다.

만약에 Human 클래스 내부에 public을 private으로 쓰면 해당하는 인자를 Human 클래스 외부에서 쓸 수 없다. 예시를 한번 보자!

image.png

age를 public이 아닌 private으로 설정하면 요렇게 에러 발생!
코딩알못일땐 굉장히 쓸모없는 기능이라고 생각했을텐데.. 그래도 몇 달 공부했다고 저 기능이 나중에 유용하게 쓰일것 같단 생각이 팍팍ㅋㅋㅋ

Block Chain 만들기

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: Block = new Block(0, "2020202020202", "", "Hello", 123456);

let blockchain: Block[] = [genesisBlock];

console.log(blockchain);

export {};

이렇게 하면 콘솔도 아주 잘 찍힌다!
여기서 테스트 한 가지… blockchain에다가 다른걸 푸쉬해보자.

blockchain.push("gold"); 를 한번 시험삼아 해보면..

image.png

짠! 이렇게 에러 발생! 클래스 안의 parameter로 쓰이지 않은 값을 푸쉬하려니 당연히 되지 않는다.

Block Chain - Two

yarn add crypto-js 명령어로 크립토 설치!
사실 니코의 다른 블록체인 강의를 듣지 못해서 블록체인 개념은 잘 모른다.
근데 일단 crypto가 해쉬를 위한 암호화 도구인걸로 이해했다!ㅋㅋ

import * as CryptoJS from "crypto-js";

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

// 여기서 static을 바깥에서 이 메소드를 부르지 못한다.
  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: Block = new Block(0, "2020202020202", "", "Hello", 123456);

let blockchain: Block[] = [genesisBlock];

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

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

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

export {};

갑자기 알 수 없는 함수들이 많아지고 있는데.. 흠.. 일단.. 따라치고 본다…
따라치다 보면 타입스크립트가 익숙해지겠지 ㅠㅠ

Block Chain - 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,
    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: Block = new Block(0, "2020202020202", "", "Hello", 123456);

let blockchain: Block[] = [genesisBlock];

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

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

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

const createNewBlock = (data: string): Block => {
  const previousBlock: Block = getLastestBlock();
  const newIndex: number = previousBlock.index + 1;
  const newTimestamp: number = getNewTimeStamp();
  const newHash: string = Block.calculateBlockHash(
    newIndex,
    previousBlock.hash,
    newTimestamp,
    data
  );
  const newBlock: Block = new Block(
    newIndex,
    newHash,
    previousBlock.hash,
    data,
    newTimestamp
  );
  return newBlock;
};

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

export {};

콘솔을 살펴보자.

image.png

값이 아주 잘 나오고 있다. crypto.js가 만들어낸 hash 넘나 신기..!
근데 보면 index 값이 동일하게 나온다. 그래서 다시 도전!!

Block Chain - Last

import * as CryptoJS from "crypto-js";

class Block {
  static calculateBlockHash = (
    index: number,
    previousHash: string,
    timestamp: number,
    data: string
  ): 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.timestamp === "number" &&
    typeof aBlock.data === "string";

  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, "2020202020202", "", "Hello", 123456);

let blockchain: Block[] = [genesisBlock];

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

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

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

const createNewBlock = (data: string): Block => {
  const previousBlock: Block = getLastestBlock();
  const newIndex: number = previousBlock.index + 1;
  const newTimestamp: number = getNewTimeStamp();
  const newHash: string = Block.calculateBlockHash(
    newIndex,
    previousBlock.hash,
    newTimestamp,
    data
  );
  const newBlock: Block = new Block(
    newIndex,
    newHash,
    previousBlock.hash,
    data,
    newTimestamp
  );
  addBlock(newBlock);
  return newBlock;
};

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

const isBlockValid = (candidateBlock: Block, previousBlock: Block): boolean => {
  if (!Block.validateStructure(candidateBlock)) {
    return false;
  } else if (previousBlock.index + 1 !== candidateBlock.index) {
    return false;
  } else if (previousBlock.hash !== candidateBlock.previousHash) {
    return false;
  } else if (getHashforBlock(candidateBlock) !== candidateBlock.hash) {
    return false;
  } else {
    return true;
  }
};

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

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

console.log(blockchain);

export {};

어디 콘솔을 한번 찍어보자..!

image.png

서로 다른 인덱스 넘버, 서로 다른 해시값! 아주 잘 찍히고 있다.
사실 블록체인은 아직도 모르겠다..ㅋㅋㅋ
예전같으면 블록체인의 원리와 역사 다 검색해서 포스팅에 넣었을텐데.. 시간이 매우 없다ㅠ
깊게 파보는건 다음 기회에!!!! 공부할 게 넘넘 많다리~~~

근데 대충 타입스크립트가 어떻게 적용되는지는 얼추 알겠다.
빠른 시일 내에 내가 CRA로 만든 페이지들을 타입스크립트로 변형해서 올릴수 있기를..😂😂

Reference

  • 노마드코더 타입스크립트로 블록체인 만들기