트랜잭션에 대한 수수료는 어떻게 계산되는가?
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 feeclass 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자가 제공하는 정보를 “검증”할 방법이 없다.
- 그렇기 때문에 트랜잭션 정보 전체를 받고, 이 트랜잭션 내용에 대한 해시값을 통과시켜 검증하는 과정을 추가한다면,
- 정확히 원하는 트랜잭션임을 확인 가능하다.