Learning Python for Forensics
上QQ阅读APP看书,第一时间看更新

Working with the print_transactions() function

This function handles the bulk of the processing logic in our code. This function traverses the transactions, or txs, list of embedded dictionaries from the loaded JSON data.

For each transaction, we'll print out its relative transaction number, the transaction hash, and the time of the transaction. Both the hash and time keys are easy to access as their values are stored in the outermost dictionary. The input and output details of the transaction are stored in an inner dictionary mapped to the input and output keys.

As is often the case, the time value is stored in Unix time. Luckily, in Chapter 2, Python Fundamentals, we wrote a script to handle such conversions, and once more we'll reuse this script by calling the unix_converter() method. The only change made to this function was removing the UTC label as these time values are stored in local time.

Because we imported unix_converter as unix, we must refer to the module as unix

Let's take a quick look at the data structure we're dealing with. Imagine if we could pause the code during execution and inspect contents of variables, such as our account variable. At this point in this book, we'll just show you the contents of the account variable at this stage of execution. Later on in this book, we'll more formally discuss debugging in Python using the pdb module.

More information on the Python Debugger ( pdb) is available in the documentation at  https://docs.python.org/3/library/pdb.html .

In the following example, we can see the keys mapped to the first transaction in the txs list within the account dictionary. The hash and time keys are mapped to string and integer objects, respectively, which we can preserve as variables in our script:

>>> print(account['txs'][0].keys())
dict_keys(['ver', 'inputs', 'weight', 'block_height', 'relayed_by',
'out', 'lock_time', 'result', 'size', 'time', 'tx_index', 'vin_sz',
'hash', 'vout_sz'])

Next, we need to access the input and output details for the transaction. Let's take a look at the out dictionary. By looking at the keys, we can immediately identify the address, addr, and value sent as being valuable information. With an understanding of the layout and what data we want to present to the user, let's take a look at how we process each transaction in the txs list:

>>> print(account['txs'][0]['out'][0].keys())
dict_keys(['spent', 'tx_index', 'type', 'addr', 'value', 'n',
'script'])

Before printing details of each transaction, we call and print basic account information parsed by the print_header() helper function to the console on line 77. On line 79, we begin to iterate through each transaction in the txs list. We've wrapped the list with the enumerate() function to update our counter, and the first variable in the for loop, i, to keep track of which transaction we're processing:

070 def print_transactions(account):
071 """
072 The print_transaction function is responsible for presenting
073 transaction details to end user.
074 :param account: The JSON decoded account and transaction data
075 :return:
076 """
077 print_header(account)
078 print('Transactions')
079 for i, tx in enumerate(account['txs']):

For each transaction, we print the relative transaction number, hash, and time. As we saw earlier, we can access hash or time by supplying the appropriate key. Remember that we do need to convert the Unix timestamp stored in the time key. We accomplish this by passing the value to the unix_converter() function:

080         print('Transaction #{}'.format(i))
081 print('Transaction Hash:', tx['hash'])
082 print('Transaction Date: {}'.format(
083 unix.unix_converter(tx['time'])))

On line 84, we begin to traverse the output list in the outside dictionary. This list is made up of multiple dictionaries with each representing an output for a given transaction. The keys we're interested in these dictionaries are the addr and value keys:

084         for outputs in tx['out']:

Be aware that the value value (not a typo) is stored as a whole number rather than a float and so a transaction of 0.025 BTC is stored as 2,500,000. We need to multiply this value by 10-8 to accurately reflect the value of the transaction. Let's call our helper function, get_inputs(), on line 85. This function will parse the input for the transaction separately and return the data in a list:

085              inputs = get_inputs(tx)

On line 86, we check to see whether there's more than one input address. That conditional will dictate what our print statement looks like. Essentially, if there's more than one input address, each address will be joined with an ampersand to clearly indicate the additional addresses.

The print statements on lines 87 and 91 use the string formatting method to appropriately display our processed data in the console. In these strings, we use the curly braces to denote three different variables. We use the join() function to convert a list into a string by joining on some delimiter. The second and third variables are the output addr and value keys, respectively:

086             if len(inputs) > 1:
087 print('{} --> {} ({:.8f} BTC)'.format(
088 ' & '.join(inputs), output['addr'],
089 outputs['value'] * 10**-8))
090 else:
091 print('{} --> {} ({:.8f} BTC)'.format(
092 ''.join(inputs), outputs['addr'],
093 outputs['value'] * 10**-8))
094
095 print('{:=^22}\n'.format(''))

Note how the designation for the value object is different from the rest. Because our value is a float, we can use string formatting to properly display the data to the correct precision. In the format descriptor, {:.8f}, the 8 represents the number of decimal places we want to allow. If there are more than eight decimal places, the value is rounded to the nearest number. f lets the format() method know that the input is expected to be of the float type. This function, while responsible for printing out the results to the user, uses two helper functions to perform its job.