text: from io import BytesIO
  from unittest import TestCase
  
  from helper import (
      little_endian_to_int,
      read_varint,
  )
  from script import Script
  
  
  class Tx:
  
      def __init__(self, version, tx_ins, tx_outs, locktime, testnet=False):
          self.version = version
          self.tx_ins = tx_ins
          self.tx_outs = tx_outs
          self.locktime = locktime
          self.testnet = testnet
  
      def __repr__(self):
          tx_ins = ' '.join([f'{tx_in}' for tx_in in self.tx_ins])
          tx_outs = ' '.join([f'{tx_out}' for tx_out in self.tx_outs])
          return f'version: {self.version}\ntx_ins:\n{tx_ins}\ntx_outs:\n{tx_outs}\nlocktime: {self.locktime}\n'
  
      @classmethod
      def parse(cls, s, testnet=False):
          '''Takes a byte stream and parses the transaction at the start
          return a Tx object
          '''
          # s.read(n) will return n bytes
          # version has 4 bytes, little-endian, interpret as int
          # num_inputs is a varint, use read_varint(s)
          # each input needs parsing
          # num_outputs is a varint, use read_varint(s)
          # each output needs parsing
          # locktime is 4 bytes, little-endian
          # return an instance of the class (cls(...))
          raise NotImplementedError
  
  
  class TxIn:
      def __init__(self, prev_tx, prev_index, script_sig=None, sequence=0xffffffff):
          self.prev_tx = prev_tx
          self.prev_index = prev_index
          if script_sig is None:
              self.script_sig = Script()
          else:
              self.script_sig = script_sig
          self.sequence = sequence
  
      def __repr__(self):
          return f'{self.prev_tx.hex()}:{prev_index}'
  
      @classmethod
      def parse(cls, s):
          '''Takes a byte stream and parses the tx_input at the start
          return a TxIn object
          '''
          # s.read(n) will return n bytes
          # prev_tx is 32 bytes, little endian
          # prev_index is 4 bytes, little endian, interpret as int
          # script_sig is a variable field (length followed by the data)
          # you can use Script.parse to get the actual script
          # sequence is 4 bytes, little-endian, interpret as int
          # return an instance of the class (cls(...))
          raise NotImplementedError
  
  
  class TxOut:
  
      def __init__(self, amount, script_pubkey):
          self.amount = amount
          self.script_pubkey = script_pubkey
  
      def __repr__(self):
          return f'{self.amount}:{self.script_pubkey}'
  
      @classmethod
      def parse(cls, s):
          '''Takes a byte stream and parses the tx_output at the start
          return a TxOut object
          '''
          # s.read(n) will return n bytes
          # amount is 8 bytes, little endian, interpret as int
          # script_pubkey is a variable field (length followed by the data)
          # you can use Script.parse to get the actual script
          # return an instance of the class (cls(...))
          raise NotImplementedError
  
  
  class TxTest(TestCase):
  
      def test_parse_version(self):
          raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
          stream = BytesIO(raw_tx)
          tx = Tx.parse(stream)
          self.assertEqual(tx.version, 1)
  
      def test_parse_inputs(self):
          raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
          stream = BytesIO(raw_tx)
          tx = Tx.parse(stream)
          self.assertEqual(len(tx.tx_ins), 1)
          want = bytes.fromhex('d1c789a9c60383bf715f3f6ad9d14b91fe55f3deb369fe5d9280cb1a01793f81')
          self.assertEqual(tx.tx_ins[0].prev_tx, want)
          self.assertEqual(tx.tx_ins[0].prev_index, 0)
          want = bytes.fromhex('6b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278a')
          self.assertEqual(tx.tx_ins[0].script_sig.serialize(), want)
          self.assertEqual(tx.tx_ins[0].sequence, 0xfffffffe)
  
      def test_parse_outputs(self):
          raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
          stream = BytesIO(raw_tx)
          tx = Tx.parse(stream)
          self.assertEqual(len(tx.tx_outs), 2)
          want = 32454049
          self.assertEqual(tx.tx_outs[0].amount, want)
          want = bytes.fromhex('1976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac')
          self.assertEqual(tx.tx_outs[0].script_pubkey.serialize(), want)
          want = 10011545
          self.assertEqual(tx.tx_outs[1].amount, want)
          want = bytes.fromhex('1976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac')
          self.assertEqual(tx.tx_outs[1].script_pubkey.serialize(), want)
  
      def test_parse_locktime(self):
          raw_tx = bytes.fromhex('0100000001813f79011acb80925dfe69b3def355fe914bd1d96a3f5f71bf8303c6a989c7d1000000006b483045022100ed81ff192e75a3fd2304004dcadb746fa5e24c5031ccfcf21320b0277457c98f02207a986d955c6e0cb35d446a89d3f56100f4d7f67801c31967743a9c8e10615bed01210349fc4e631e3624a545de3f89f5d8684c7b8138bd94bdd531d2e213bf016b278afeffffff02a135ef01000000001976a914bc3b654dca7e56b04dca18f2566cdaf02e8d9ada88ac99c39800000000001976a9141c4bc762dd5423e332166702cb75f40df79fea1288ac19430600')
          stream = BytesIO(raw_tx)
          tx = Tx.parse(stream)
          self.assertEqual(tx.locktime, 410393)
  