본문 바로가기
블록체인

transaction 생성

by 혀닙 2022. 6. 22.

Transaction 생성 및 UTXO를 업데이트하는 코드를 작성해보자

코드의 실행 순서는 다음과 같이 진행될 것이다 .

 

 

1. 코드 실행 순서

1) Wallet 서버에서 transaction 정보 전송 > axios.post('/sendTransaction')

2) BlockChain 서버의 /sendTransaction에서 sendTransaction() 메서드 실행

 

3) sendTransaction(\_receivedTx,unspentTxOuts)


   3-1) 서명 검증: Wallet.getVerify()
   3-2) 발신지갑 최신화 : const myWallet = new this()
   3-3) 발신지갑의 잔액과 amount 비교
   3-4) transaction 생성


4) 내 계정정보와 일치하는 UTXO 가져오기: getMyUTXO() 생성


5) createTransaction() 메서드 실행

   5-1) sum과 txins 생성: createTxIns()
   5-2) txouts 생성: createTxOuts()
   5-3) new Transaction 생성: const tx = new Transaction(txins,txouts)
   5-4) UTXO 업데이트

 

 

 

2. 실행 코드 작성

1-1) req.body 정보 받아서 sendTransaction() 메서드 호출

/* index.ts */

app.post('/sendTransaction', (req, res) => {
    try {
        const receivedTx: ReceivedTx = req.body;
        // post 형식으로 받은 body의 내용과 utxo 배열을 매개변수로 sendTransaction() 메서드 실행
        Wallet.sendTransaction(receivedTx, ws.getUnspentTxOuts());
    } catch (e) {
        if (e instanceof Error) console.error(e.message);
    }
    res.json([]);
});

 

2-1) sendTransaction 메서드 () 실행

/* @core/wallet/wallet.ts */
    
export class Wallet {
    //...생략
    
    static sendTransaction(_receivedTx: any, _unspentTxOuts: UnspentTxOut[]): Transaction {
        // 1. 서명 검증
        const verify = Wallet.getVerify(_receivedTx);
        if (verify.isError) throw new Error(verify.error);
        
        // 2. 발신인의 지갑 정보 최신화
        const myWallet = new this(_receivedTx.sender, _receivedTx.signature, _unspentTxOuts);
        
        // 3. 최신화된 정보를 가지고 balance 체크
        if (myWallet.balance < _receivedTx.amount) throw new Error('금액이 부족합니다');
        
        // 4. transaction을 만드는 과정 - createTransaction() 메서드 생성
        const myUTXO = UnspentTxOut.getMyUnspentTxOuts(myWallet.account, _unspentTxOuts);
        const tx = Transaction.createTransaction(_receivedTx, myUTXO);
        
        return tx;
    }

    static getVerify(_receivedTx: ReceivedTx): Failable<undefined, string> {
        const { sender, received, amount, signature } = _receivedTx;
        const data: [string, string, number] = [sender, received, amount];
        const hash: string = SHA256(data.join('')).toString();

        const keyPair = ec.keyFromPublic(sender, 'hex');
        const isVerify = keyPair.verify(hash, signature);
        if (!isVerify) return { isError: true, error: '서명이 올바르지 않습니다' };
        return { isError: false, value: undefined };
    }
    
        //...생략

}

 

 

3) 내 계정과 일치하는 UTXO 가져오기

/* /transaction/unspentTxOut.ts */

export class UnspentTxOut{

	//...생략
    
	static getMyUnspentTxOuts(_account: string, _unspentTxOuts: UnspentTxOut[]): UnspentTxOut[] {
        //전체 UTXO 중에서 매개변수로 받은 내 계정과 일치하는 UTXO 정보를 필터 메서드로 걸러내기
        return _unspentTxOuts.filter((utxo: UnspentTxOut) => utxo.account === _account);
    }
}

 

 

4) transaction 생성

/* /transaction/transaction.ts */

export class Transaction {

	// ...생략
    
	static createTransaction(_receivedTx: any, _myUTXO: UnspentTxOut[]): Transaction {
        // 1. txIn을 생성
        const { sum, txins } = TxIn.createTxIns(_receivedTx, _myUTXO);

        // 2.서명 생성

        // 3. txIn 기반으로 txOut 생성
        const txouts: TxOut[] = TxOut.createTxOuts(sum, _receivedTx);
        
        // 4. txIn과 txOut 기반으로 new Transaction 생성
        const tx = new Transaction(txins, txouts);
        return tx;
    }
}

 

4-1) txins 생성

/* /transaction/txin.ts */
export class txIn {

	//...생략
    
	static createTxIns(_receivedTx: any, _myUTXO: IUnspentTxOut[]) {
        let sum = 0;
        let txins: TxIn[] = [];
        for (let i = 0; i < _myUTXO.length; i++) {
            //myUTXO = [20, 20, 20, 20, 20], amount = 70이라고 가정
            const { txOutId, txOutIndex, amount } = _myUTXO[i];
            const item: TxIn = new TxIn(txOutId, txOutIndex, _receivedTx.signature);
            txins.push(item);
            sum += amount;
            if (sum >= _receivedTx.amount) return { sum, txins };
        }
        // 참고, for(const utxo of myUTXO): 같은 문법 for (let i = 0; i < myUTXO.length; i++) {
        return { sum, txins };
    }
}

 

4-2) txouts 생성

/* /transaction/txout.tx */
export class TxOut{

	//...생략
    
	static createTxOuts(_sum: number, _receivedTx: any): TxOut[] {
        const { amount, sender, received } = _receivedTx; //보낼 금액, 보내는사람 '공개키', 받는사람 '계정'
        const senderAccount: string = Wallet.getAccount(sender);
        const receivedTxOut = new TxOut(received, amount);
        const senderTxOut = new TxOut(senderAccount, _sum - amount);
        if (senderTxOut.amount <= 0) return [receivedTxOut];

        return [receivedTxOut, senderTxOut];
    }
}

 

 

 

3. 테스트 코드 작성

import { Chain } from '@core/blockchain/chain';
import { Wallet } from '@core/wallet/wallet';

describe('chain 함수 체크', () => {
    let ws: Chain = new Chain(); //[GENESIS]
    let receivedTx = {
        sender: '02fdfe50d7926f63a76b46e913ee689085e094b4ddf17cc1a3fb9205a90e6a7c78',	//발신인의 공개키
        received: ' 67b776aa654b0033bac7b47028e17bc02361f28c',	//수신인의 계정
        amount: 70,
        signature: {
            r: '2c1e7908ec578df05089fe568551a7f36003bfb3122169c9ec75455bdcbd1c96',
            s: '6df02fd7fc3d3f382aac8f72c2f699d37601f7b1e28c42b2da9b493e00e90b8b',
            recoveryParam: 1,
        },
    };
    it('addBlock 함수 체크', () => {
        // ' 02fdfe50d7926f63a76b46e913ee689085e094b4ddf17cc1a3fb9205a90e6a7c78';
        ws.miningBlock('ee689085e094b4ddf17cc1a3fb9205a90e6a7c78');
        ws.miningBlock('ee689085e094b4ddf17cc1a3fb9205a90e6a7c78');
        ws.miningBlock('ee689085e094b4ddf17cc1a3fb9205a90e6a7c78');
        ws.miningBlock('ee689085e094b4ddf17cc1a3fb9205a90e6a7c78');
        ws.miningBlock('ee689085e094b4ddf17cc1a3fb9205a90e6a7c78');
    });

    it('transaction 검증', () => {
        try {
            const tx = Wallet.sendTransaction(receivedTx, ws.getUnspentTxOuts());
            console.log(tx)
        } catch (e) {
            if (e instanceof Error) console.error(e.message);
        }
    });
});

 

4. 테스트 코드 결과

20BTC씩 5번 채굴하여 100BTC를 보유하고 있는 계정에서

70BTC를 산출하는 거래 내역에 대한 테스트 코드 결과이다.

 

console.log(tx)
// Transaction {
//     txIns: [
//       TxIn {
//         txOutId: '949c07d67ff61c035f3387e09c99e6c473e256741ace844b1ba715ee09395d62',
//         txOutIndex: 0,
//         signature: [Object]
//       },
//       TxIn {
//         txOutId: '1e70680e2477943cf7594a12ed7a3525c545933df854376165f7db551997b82a',
//         txOutIndex: 0,
//         signature: [Object]
//       },
//       TxIn {
//         txOutId: '5196bd745609705d9994360759a906e9585f90eb676827eeb5caa71167ff84f8',
//         txOutIndex: 0,
//         signature: [Object]
//       },
//       TxIn {
//         txOutId: '5ff01642533dc33a26d63c3848a9cdf1bc58f35f72f0b253eb290fbb00d09b42',
//         txOutIndex: 0,
//         signature: [Object]
//       }
//     ],
//     txOuts: [
//       TxOut {
//         account: ' 67b776aa654b0033bac7b47028e17bc02361f28c',
//         amount: 70
//       },
//       TxOut {
//         account: 'ee689085e094b4ddf17cc1a3fb9205a90e6a7c78',
//         amount: 10
//       }
//     ],
//     hash: '8e6195aae6c38fbf00acf7d5838f54656536bc9ccbcfd49bd1ee0ebcea4d8520'
//   }

댓글