728x90
반응형
728x170

Typescript로 블록체인 만들기


VSCode TypeScript 준비


  1. tsconfig.json 생성
    (파일을 쓸 때에는 주석의 한글 설명을 삭제해야 정상 실행됩니다.)
  {
      "compilerOptions": { //컴파일 옵션 설정 
        "module": "commonjs", 
        "target": "es2015", 
        "sourceMap": true 
  }, 
     "include": ["index.ts"], //컴파일과정에서 포함할 파일 
     "exclude": ["node_modules"], //컴파일과정에서 제외 
    }
  1. index.ts 생성
  console.log("test");
  1. 터미널 실행
    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/ckdqja135/Typescript-restful-starter/tree/master/typechain", 
    "author": "cbsong <ckdqja13580@gmail.com>", 
    "license": "MIT",
    "scripts": { 
      "prestart": "tsc", //타입스크립트 실행 전에 tsc 명령어로 js로 컴파일 시켜줍니다.
      "start": "node index.js" //js화 된 파일을 실행. 
    } 
  }
  1. 타입스크립트 실행

    yarn start
    
--------------

1. 기본 함수 예제
``` typescript

  const name = "changbeom",
    age = 26,
    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 = "changbeom",
      age = 26,
      gender = "Male";

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

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

  1. 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
  1. 설정 파일 옵션 변경

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"], //컴파일과정에서 제외 
  }
  1. 폴더, 파일 정리

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

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

  • index.js, index.js.map 은 dist 폴더에 생성되게 되므로 기존에 생성된 파일은 삭제해줍니다.
  1. 실행

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

# Interface in Typescript
파라미터 값들을 Object로 넘기고 싶을 때는 Interface 를 사용합니다.
```typescript

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

  const person = { 
    name : "changbeom", 
    age : 26, 
    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 changbeom = new Human("changbeom", 26, "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'; 명령어로 import해줍니다.

* HASH함수를 이용한 블록 생성
```typescript

  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 {};

위 코드를 실행하면 아래와 같이 출력이 되는데 아직 블록체인에 push 하지 않았기 때문에 Index가 둘 다 1로 출력됩니다.

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);
    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);

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

 

code


code

728x90
반응형
그리드형
Bami