declare interface ITxIn {
txOutId: string; // 트랜잭션 해시값 ( 보내는 사람, 금액 좌표 )
txOutIndex: number; // 배열의 인덱스 값 ( 보내는 사람, 금액 좌표 )
signature?: string | undefined; // 서명 ( 보내는 사람의 서명 )
}
declare interface ITxOut {
account: string; // 지갑 주소
amount: number; // 금액
}
declare interface ITransaction {
hash: string; // Transaction Hash 값
txOuts: ITxOut[]; // Output 정보들 , 배열안에 객체로 들어간다.
txIns: ITxIn[]; // Input 정보들 , 배열안에 객체로 들어간다.
}
declare interface unspentTxOut {
txOutId: string; // Transaction Hash 값
txOutIndex: number; // UTXO 리스트의 index값
account: string; // 지갑 주소
amount: number; // 금액
}
지난 시간에 만들어둔
Transaction
의Type
을 정해주는Transaction interface
를 활용하여Transaction class
를 만들어주도록 하겠습니다.
export class TxIn {
public txOutId: string; // Transaction 해시값
public txOutIndex: number; // Transaction Output 리스트의 배열의 인덱스 값
public signature?: string; // 코인베이스는 signature가 없으니 ? (Optional chaining) 연산자를 사용하여 값이 null 또는 undefined라도 error가 아닌 undefined를 리턴하도록 합니다.
constructor(_txOutId: string, _txOutIndex: number, _signature: string | undefined = undefined) {
this.txOutId = _txOutId;
this.txOutIndex = _txOutIndex;
this.signature = _signature;
}
}
Transaction
의Input
내용인Txin
에서는Transaction
의 고유한hash
값인txOutId
와 이hash
에 해당하는Transaction
의Ouputs
배열안에 몇번째 값을 사용한것인지 알 수 있도록txOutIndex
값을 추가하였습니다.
signature
는 코인베이스에서는Input
의 내용이 없기 때문에 ?(Optional chaining)
연산자를 사용하여 값이null or undefined
라도error
를return
하지 않고undefined
를return
하도록 하였습니다.
export class TxOut {
public account: string; // 지갑 주소
public amount: number; // 금액
constructor(_account: string, _amount: number) {
this.account = _account;
this.amount = _amount;
}
}
Transaction의 Output 내용인 Txout에서는 지갑주소와 금액 인스턴스를 생성하도록 해주었습니다.
export class unspentTxOut {
public txOutId: string;
public txOutIndex: number;
public account: string;
public amount: number;
constructor(_txOutId: string, _txOutIndex: number, _account: string, _amount: number){
this.txOutId = _txOutId; // Transaction의 해쉬값
this.txOutIndex = _txOutIndex; // Transaction의 Output에 있는 각 객체의 Index번호
this.account = _account; // 지갑 주소
this.amount = _amount; // 금액
}
}
unspentTxOut
은txOut
객체에 있는 내용과Transaction
의 내용을 가지고 만들어줍니다.
txOutId
와txOutIndex
로 해당Transaction
의 고유한hash
값과 해당hash
값에 해당하는Transaction
의Output
배열안에 몇번째 인덱스인지 알 수 있도록txOutIndex
값을 추가하였습니다.
마지막으로 어느 지갑에 얼마의 금액이 들어있는지 알아야 하니account
와amount
를 추가해주었습니다.
import { SHA256 } from 'crypto-js';
import { TxIn } from './txin';
import { TxOut } from './txout';
import { unspentTxOut } from './unspentTxOut';
export class Transaction {
public hash: string;
public txIns: TxIn[];
public txOuts: TxOut[];
constructor(_txIns: TxIn[], _txOuts: TxOut[]) {
this.txIns = _txIns; // Transaction의 input의 내용들
this.txOuts = _txOuts; // Transaction의 output의 내용들
this.hash = this.createTransactionHash(); // Transaction의 고유한 값
}
// Transaction hash 만들기
createTransactionHash(): string {
// txoutput 내용의 각 객체의 value값들을 스트링으로 이어붙임
const txoutConmtent: string = this.txOuts.map((v) => Object.values(v).join('')).join('');
// txinput 내용의 각 객체의 value값들을 스트링으로 이어붙임
const txinContent: string = this.txIns.map((v) => Object.values(v).join('')).join('');
// 이어붙인 txoutConmtent + txinContent 값을 해쉬화하여 리턴
return SHA256(txoutConmtent + txinContent).toString();
}
// UTXO 생성하기 -> 배열안에 객체 형태로 저장
createUTXO(): unspentTxOut[] {
// txOutId = Transaction의 해쉬값
// txOutIndex = Transaction의 Output에 있는 각 객체의 Index번호
// account = 지갑 주소
// amount = 금액
return this.txOuts.map((v, i) => {
return new unspentTxOut(this.hash, i, v.account, v.amount);
});
}
}
Transaction class
에서는Transaction
의 고유한 값인hash
값과txIns
(Input 내용)
,txOuts
(Output 내용)
을 추가하였습니다.
각Transaction
의 고유한값인hash
값을 생성할때는txIns
배열안에 있는 객체들의value
값과txOuts
배열안에 있는 객체들의value
값을 문자열로 이어붙이고 해당 값을 해쉬화하여 만들어주었습니다.
마지막으로Transaction
을 생성하고 나서UTXO (미사용 트랜잭션 출력값)
에 넣어주기 위해txOuts
배열안에 있는 객체들에hash
값과index
값을 추가하여return
해주는createUTXO
메서드를 만들어 주었습니다.
이제 블록을 생성할때 자동으로 코인베이스 (블록의 첫번째 Transaction값으로 채굴자에 대한 보상 내용이 들어갑니다.)
를 생성하도록 HTTP 서버에 블록을 생성하는 API를 수정하도록 하겠습니다.
/* index.ts ( 블록체인 HTTP 서버 ) */
// 블록채굴 API
app.post('/mineBlock', (req, res) => {
const { data } = req.body; // Transaction 객체를 채우기 위한 정보로 지갑주소값이 들어감
const newBlock = ws.miningBlock(data); // 받는 사람의 지갑 주소를 인자값으로 보내줌
if (newBlock.isError) return res.status(500).send(newBlock.error);
res.json(newBlock.value);
});
mineBlock으로 요청이 들어오면 지갑주소를 req.body로 받아오고 해당 지갑주소를 인자값으로 넣어 miningBlock 함수를 실행합니다.
/* chain.ts */
public miningBlock(_account: string): Failable<Block, string> {
const txin: ITxIn = new TxIn('', this.getLastestBlock().height + 1); // 해시값이 겹치지 않도록 this.getLastestBlock().height + 1를 넣어주었다.
const txout: ITxOut = new TxOut(_account, 50); // 지갑주소와 블록을 생성했을때 받는 금액으로 OutPut내용을 만든다.
const transaction: Transaction = new Transaction([txin], [txout]); // input 배열의 내용들과 output 배열의 내용들로 Transaction을 만든다.
return this.addBlcok([transaction, ...this.getTransactionPool()]);
}
// 블록을 추가할 때 실행할 함수
public addBlcok(data: ITransaction[]): Failable<Block, string> {
const previousBlock = this.getLastestBlock();
const adjustmentBlock: Block = this.getAdjustmentBlock();
const newBlock = Block.generateBlock(previousBlock, data, adjustmentBlock);
const isVaild = Block.isValidNewBlock(newBlock, previousBlock);
if (isVaild.isError) return { isError: true, error: isVaild.error };
this.blockchain.push(newBlock);
newBlock.data.forEach((_tx: ITransaction) => {
this.updateUTXO(_tx);
});
// 사용한 트랜잭션을 트랜잭션풀에서 제거해주어야 합니다.
this.updateTransactionPool(newBlock);
return { isError: false, value: newBlock };
}
chain.ts
에 있는miningBlock
을 보면txin
의 내용과txout
의 내용을 가지고 채굴에 대한 보상내용이 있는Transaction
을 생성하고 이Transaction
을addBlock
함수의 인자값으로 넣어주면Block
의data
부분에miningBlock
에서 채굴에 보상에 대한 내용으로 생성한Transaction
이 들어간 상태로 블록을 생성하게 됩니다.