제네시스 블록으로부터 다음 블록을 생성하려면 생성될 블록은 이전 블록에 대한 정보를 참조할 수 있어야 합니다
그리고 이 생성 준비과정에 도달하려면 블록 해시를 제외한 모든 데이터가 완성된 상태여야 합니다
// 두번째 블록 생성을 위한 생성자
class Block {
constructor() {}
// 직전 블록 데이터를 의존합니다
createBlockInfo(previousBlock: IBlock): BlockInfo {
// const blockInfo: BlockInfo = {
// version: VERSION,
// height: previousBlock.height + 1,
// timestamp: new Date().getTime(),
// previousHash: previousBlock.hash,
// nonce: 0,
// difficulty: 0
// }
// return blockInfo;
// ↓ 속성값을 대입하는 방식이 요즘 트렌드라는데...
const blockInfo = new BlockInfo()
blockInfo.version = VERSION
blockInfo.height = previousBlock.height + 1
blockInfo.timestamp = new Date().getTime()
return blockInfo
}
}
export default Block
블록 생성은 이전 블록 데이터에 대한 신뢰를 전제로 합니다
그래서 새 블록을 생성하기 전에 이전 블록의 해시값이 올바른지에 대한 별도의 검증 과정이 필요합니다
여러 방법론이 있을 수 있겠지만 여기서는 직전 블록의 평문 데이터를 가져와서 또 한 번 해시화를 진행한 뒤
직전 블록이 가진 해시 속성과의 일치여부를 검사하는 방법을 택했습니다
직전 블록의 신뢰성을 검증하는 함수를 생성합니다
// crypto.createBlockHash() === block.hash ?
isValidPreviousBlock(previousBlock: IBlock): void {
this.crypto.isValidHash(previousBlock.hash)
const validHash = this.crypto.createBlockHash(previousBlock)
if (validHash !== previousBlock.hash) throw new Error(`이전 블록의 해시값이 올바르지 않습니다 ${validHash} !== ${previousBlock.hash}`)
}
테스트 코드
describe('Block', () => {
let block: Block
let crypto: CryptoModule
beforeEach(() => {
crypto = new CryptoModule()
block = new Block(crypto) // 인스턴스가 crypto의 메서드를 사용할 수 있도록
})
describe('isValidPreviousBlock', () => {
let previousBlock: IBlock
beforeEach(() => { previousBlock = { ...GENESIS } })
it(('매개변수로 넘겨받은 블럭 해시값이 올바른가'), () => {
expect(() => block.isValidPreviousBlock(previousBlock)).not.toThrowError() // not은 반대 입장을 검사
})
it(('매개변수로 넘겨받은 블럭 해시값이 올바르지 않을 경우 에러가 발생하는가'), () => {
previousBlock.hash = "84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8371"
expect(() => block.isValidPreviousBlock(previousBlock)).toThrowError()
})
it(('블럭 해시값이 변조된 적이 있는가'), () => {
block.isValidPreviousBlock(previousBlock)
})
it(('블럭 해시값이 올바르지 않을 때 에러가 발생하는가'), () => {
})
})
describe(('createBlockInfo'), () => {
const previousBlock = GENESIS
it(('createBlockHash 메서드가 존재하는가'), () => {
expect(typeof block.createBlockInfo).toBe("function")
})
it(('createBlock에서 BlockInfo가 잘 생성되는가'), () => {
const newBlock = block.createBlockInfo(previousBlock)
expect(typeof newBlock).toBe("object") // failed ~ undefined
})
it(('createBlock에서 BlockInfo의 내용이 올바른가'), () => {
const newBlock = block.createBlockInfo(previousBlock)
expect(newBlock.previousHash).toBe(previousBlock.hash)
expect(newBlock.height).toBe(previousBlock.height + 1)
})
})
})
2번째 블록을 생성할 함수(createBlock()
)에는 작업증명(POW)에 관한 로직이 담겨야 합니다
그리고 난이도 조절을 위해서는 10번째로 생성될 블록의 정보도 필요합니다
합의 알고리즘
POW (Proof Of Work): 작업증명
POS (Proof Of Stake): 지분증명
POA (Proof Of Authority): 권한증명
그러면 작업증명을 위한 코드 설계를 시작...
전략 패턴을 사용해서 증명 방식을 쉽게 갈아끼울 수 있는 형태로 클래스를 설계합니다
같은 인터페이스를 사용하되 내부 로직(POS or POW)만 달라지는 형태로 구현해야 합니다
클래스 설계도 ERD와 마찬가지로 스키마를 그리는 과정이 필요합니다
이를 UML(Unified Modeling Language)이라고 합니다
UML 예제
테스트 코드 예제
describe('WorkProof', () => {
let workProof: WorkProof
let proof: Proof
describe('POW', () => {
beforeEach(() => {
proof = new ProofOfWork()
workProof = new WorkProof(proof)
})
it(('POW 실행'), () => {
workProof.run()
})
})
describe('POS', () => {
beforeEach(() => {
proof = new ProofOfStake()
workProof = new WorkProof(proof)
})
it(('POS 실행'), () => {
workProof.run()
})
})
})
POW 방식으로 블록 100개 생성하기
// 제네시스 블록을 체인(블록 배열)에 담은 상태에서 시작합니다
const BlockList:IBlock[] = [ GENESIS ]
for (let i = 1; i < 100; i++) {
const adjustmentBlock = (i >= 19) ? BlockList[Math.floor(i / 10)*10-10] : GENESIS
// 3번째 인자를 통해 난이도 조절. (20-29번 블록의 생성 난이도는 10번째 블록을 기준으로 합니다)
const bitcoin = block.createBlock(BlockList[i-1], "data", adjustmentBlock)
BlockList.push(bitcoin)
}
console.log(BlockList)
/**
[
{
version: '1.0.0',
height: 0,
timestamp: 1231006506,
previousHash: '0000000000000000000000000000000000000000000000000000000000000000',
merkleRoot: 'DC24B19FB7508611ACD8AD17F401753670CFD8DD1BEBEF9C875125E98D82E3D8',
nonce: 0,
difficulty: 0,
hash: '84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8370',
data: '2009년 1월 3일 더 타임스, 은행들의 두번째 구제금융을 앞두고 있는 U.K 재무장관'
},
{
nonce: 1,
difficulty: 0,
version: '1.0.0',
height: 1,
timestamp: 1682640185673,
previousHash: '84ffab55c48e36cc480e2fd4c4bb0dc5ee1bb2d41a4f2a78a1533a8bb7df8370',
merkleRoot: '3A6EB0790F39AC87C94F3856B2DD2C5D110E6811602261A9A923D3BB23ADC8B7',
data: 'data',
hash: 'a07aef620d2ba153b5173a42371db8fbd2da5873eecc82c0ef89890461c6d0e1'
},
...
{
nonce: 3,
difficulty: 2,
version: '1.0.0',
height: 100,
timestamp: 1682640185677,
previousHash: '16b455db348f232bba09ca26ac21d798ff323390ef1bf468abaef573843f9f2c',
merkleRoot: '3A6EB0790F39AC87C94F3856B2DD2C5D110E6811602261A9A923D3BB23ADC8B7',
data: 'data',
hash: '335fb98039f66c68f16e9a2a761bb2d9dd2a08bba90b7d7c62bfe2d54b58c21e'
}
]
*/