ํŠธ๋žœ์žญ์…˜์— ๋Œ€ํ•œ ์ˆ˜์ˆ˜๋ฃŒ๋Š” ์–ด๋–ป๊ฒŒ ๊ณ„์‚ฐ๋˜๋Š”๊ฐ€?

Transaction Fee

  • ๋ณด์ƒ์ด ์ฃผ์–ด์ง€์ง€ ์•Š์œผ๋ฉด ๋ˆ„๊ฐ€ ์ผ์„ ํ• ๊นŒ?
  • ์ฑ„๊ตด์ž๋Š” ๊ฑฐ๋ž˜ ๋‚ด์—ญ์„ ์ •๋ฆฌํ•˜๊ณ  ๋ธ”๋ก์„ ๋งŒ๋“ค์–ด ์ถ”๊ฐ€ํ•จ์œผ๋กœ์„œ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ๋ฐ›๋Š”๋‹ค.
  • (๋ฌผ๋ก  ์ถ”๊ฐ€์ ์œผ๋กœ ์ฑ„๊ตด๋ณด์ƒ๋„ ์žˆ๋‹ค.)
  • ๊ทธ๋ ‡๋‹ค๋ฉด ์ด ์ˆ˜์ˆ˜๋ฃŒ๋Š” ๋ˆ„๊ฐ€ ์ง€๋ถˆํ•˜๋Š” ๊ฑธ๊นŒ?
  • ํŠธ๋ž™์žญ์…˜์„ ๋งŒ๋“ค์–ด ๋ณด๋‚ผ ๋•Œ ์ด ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ๋ณด๋‚ด๋ฉด ๋œ๋‹ค.
  • ๊ทธ๋ ‡๋‹ค๋ฉด, ํŠธ๋žœ์žญ์…˜์€ ์ž…๋ ฅ๊ณผ ์ถœ๋ ฅ์œผ๋กœ ๊ตฌ์„ฑ๋˜์—ˆ๋‹ค ํ–ˆ๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด
  • ์ž…๋ ฅ์˜ ํ•ฉ์ด ์ถœ๋ ฅ์˜ ํ•ฉ๋ณด๋‹ค ๋” ํฌ๋ฉด ๋œ๋‹ค.
  • ๊ทธ๋Ÿฐ๋ฐ, ์ž…๋ ฅ์— ๋Œ€ํ•ด ๋ฐฐ์› ์„ ๋•Œ ์ž…๋ ฅ์— ๋Œ€ํ•œ ๊ธˆ์•ก์€ ํ•„๋“œ์— ์—†์—ˆ๋‹ค. (์ถœ๋ ฅ์—๋Š” ์žˆ์—ˆ๋‹ค.)
  • ๊ทธ๋Ÿฌ๋ฉด ์ž…๋ ฅ์˜ ๊ธˆ์•ก์€ ์–ด๋–ป๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์„๊นŒ?
  • ์ž…๋ ฅ์œผ๋กœ ๋“ค์–ด์˜จ ๊ฒƒ๋“ค์— ๋Œ€ํ•ด UTXO๋ฅผ ์ฐพ์•„์„œ ๊ธˆ์•ก์„ ํ™•์ธํ•˜๋ฉด ๋œ๋‹ค.
  • ์ž…๋ ฅ์—๋Š” ์ด์ „ ํŠธ๋žœ์žญ์…˜ hex๊ฐ€ ์žˆ๊ณ , ์ด๊ฑธ๋กœ ํ’€๋…ธ๋“œ๋กœ ๋ถ€ํ„ฐ ์ฐพ์€ ๋’ค, ๊ฑฐ๊ธฐ์„œ output์— ํ•ด๋‹นํ–ˆ๋˜ ํ˜„์žฌ์˜ input์„ ์ฐพ์•„ ๊ธˆ์•ก์„ ์ฝ์œผ๋ฉด ๋œ๋‹ค.
  • ํ’€๋…ธ๋“œ๋ผ๋ฉด ๋ฐ”๋กœ ์ฐพ์œผ๋ฉด๋˜๊ณ , ์•„๋‹ˆ๋ผ๋ฉด ๋ฏฟ์„ ์ˆ˜ ์žˆ๋Š” ์ œ 3์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํ’€๋…ธ๋“œ๋กœ ๋ถ€ํ„ฐ ์ •๋ณด๋ฅผ ์–ป์–ด์•ผ ํ•œ๋‹ค.
  • ๊ทธ๋Ÿฌ๋ ค๋ฉด ์ž…๋ ฅ์— ์ ํ˜€์žˆ๋˜ ์ด์ „ ํŠธ๋žœ์žญ์…˜ hex๋กœ ์ž…๋ ฅ์ด ๊ณผ๊ฑฐ์˜ output์œผ๋กœ ์žˆ์—ˆ๋˜ transaction์„ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
  • ์ด๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.
class TxFetcher:
    cache = {}
 
    @classmethod
    def get_url(cls, testnet=False):
        if testnet:
            return 'https://blockstream.info/testnet/api/'
        else:
            return 'https://blockstream.info/api/'
 
    @classmethod
    def fetch(cls, tx_id, testnet=False, fresh=False):
        if fresh or (tx_id not in cls.cache):
            url = '{}/tx/{}/hex'.format(cls.get_url(testnet), tx_id)
            response = requests.get(url)
            try:
                raw = bytes.fromhex(response.text.strip())
            except ValueError:
                raise ValueError('unexpected response: {}'.format(response.text))
            if raw[4] == 0:
                raw = raw[:4] + raw[6:]
                tx = Tx.parse(BytesIO(raw), testnet=testnet)
                tx.locktime = little_endian_to_int(raw[-4:])
            else:
                tx = Tx.parse(BytesIO(raw), testnet=testnet)
            if tx.id() != tx_id:  # ๋„คํŠธ์›Œํฌ์—์„œ ๋ฐ›์•„์˜จ Transaction๊ณผ ๋‚ด๊ฐ€ ์š”์ฒญํ•œ Transaction์„ ๋น„๊ต
                raise ValueError('not the same id: {} vs {}'.format(tx.id(), 
                                  tx_id))
            cls.cache[tx_id] = tx
        cls.cache[tx_id].testnet = testnet
        return cls.cache[tx_id]
 
    @classmethod
    def load_cache(cls, filename):
        disk_cache = json.loads(open(filename, 'r').read())
        for k, raw_hex in disk_cache.items():
            raw = bytes.fromhex(raw_hex)
            if raw[4] == 0:
                raw = raw[:4] + raw[6:]
                tx = Tx.parse(BytesIO(raw))
                tx.locktime = little_endian_to_int(raw[-4:])
            else:
                tx = Tx.parse(BytesIO(raw))
            cls.cache[k] = tx
 
    @classmethod
    def dump_cache(cls, filename):
        with open(filename, 'w') as f:
            to_dump = {k: tx.serialize().hex() for k, tx in cls.cache.items()}
            s = json.dumps(to_dump, sort_keys=True, indent=4)
            f.write(s)
 
class Tx:
 
    ...
 
    def fee(self):
        '''Returns the fee of this transaction in satoshi'''
        # initialize input sum and output sum
        input_sum = 0
        output_sum = 0
        # use TxIn.value() to sum up the input amounts
        input_sum = sum([tx_in.value() for tx_in in self.tx_ins])
        # use TxOut.amount to sum up the output amounts
        output_sum = sum([tx_out.amount for tx_out in self.tx_outs])
        # fee is input sum - output sum
        fee = input_sum - output_sum
        return fee
class TxIn:
    ...
 
    def fetch_tx(self, testnet=False):
        return TxFetcher.fetch(self.prev_tx.hex(), testnet=testnet)
 
    def value(self, testnet=False):
        '''Get the output value by looking up the tx hash.
        Returns the amount in satoshi.
        '''
        tx = self.fetch_tx(testnet=testnet)
        return tx.tx_outs[self.prev_index].amount
 
  • fetch ๋ฉ”์†Œ๋“œ๋Š” ํŠธ๋žœ์žญ์…˜์˜ id๋ฅผ ๋ฐ›์•„์„œ ํ•ด๋‹น ํŠธ๋žœ์žญ์…˜์„ ๊ฐ€์ ธ์˜จ๋‹ค.
  • ์ด ๋•Œ, TxFetcher ํด๋ž˜์Šค๋Š” ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋ฏธ ๊ฐ€์ ธ์˜จ ํŠธ๋žœ์žญ์…˜์€ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š”๋‹ค.
  • ๊ทธ๋ฆฌ๊ณ  ๋„คํŠธ์›Œํฌ์—์„œ ํŠธ๋žœ์žญ์…˜์„ ๊ฐ€์ ธ์˜ฌ ๋•Œ, ํŠธ๋žœ์žญ์…˜์˜ id์™€ ๋„คํŠธ์›Œํฌ์—์„œ ๊ฐ€์ ธ์˜จ ํŠธ๋žœ์žญ์…˜์˜ id๊ฐ€ ๊ฐ™์€์ง€ ํ™•์ธํ•œ๋‹ค.
  • ๋งŒ์•ฝ, ๋„คํŠธ์›Œํฌ์—์„œ ์š”์ฒญํ•œ ๊ฒฐ๊ณผ๊ฐ€ ํŠธ๋žœ์žญ์…˜์ด ์•„๋‹ˆ๊ณ , ๋‚ด๊ฐ€ ์š”์ฒญํ•œ ์ž…๋ ฅ์˜ ๊ธˆ์•ก์„ ๋ฐ˜ํ™˜๋ฐ›์•˜๋‹ค๋ฉด,
  • ์ œ3์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ •๋ณด๋ฅผ โ€œ๊ฒ€์ฆโ€ํ•  ๋ฐฉ๋ฒ•์ด ์—†๋‹ค.
  • ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ํŠธ๋žœ์žญ์…˜ ์ •๋ณด ์ „์ฒด๋ฅผ ๋ฐ›๊ณ , ์ด ํŠธ๋žœ์žญ์…˜ ๋‚ด์šฉ์— ๋Œ€ํ•œ ํ•ด์‹œ๊ฐ’์„ ํ†ต๊ณผ์‹œ์ผœ ๊ฒ€์ฆํ•˜๋Š” ๊ณผ์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด,
  • ์ •ํ™•ํžˆ ์›ํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์ž„์„ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.

Reference