Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save NicolasFlamel1/27465c7ad426863c8ae4a76d2219cceb to your computer and use it in GitHub Desktop.
Save NicolasFlamel1/27465c7ad426863c8ae4a76d2219cceb to your computer and use it in GitHub Desktop.
# Scans all blocks from a Grin archive node to reconstruct a wallet's transaction history. All applicable UTXOs and when they were spent are logged to a file, and a brief overview of the wallet's transaction history is shown in the console. This code lacks any error checking. A wallet's rewind hash can be obtained by running the command `grin-wallet rewind_hash` with the Grin CLI wallet, https://github.com/mimblewimble/grin-wallet.
# Imports
from secp256k1_zkp_mw import *
from hashlib import blake2b
import requests
import time
from datetime import datetime
# Constants
# Rewind hash
REWIND_HASH = bytes.fromhex('99d92c1264de3edab618eeeb024533447c30c4d6549d70efc745fe385bd2862b')
# Server
SERVER = 'http://localhost:3413'
# Functions
# Format number
def formatNumber(number):
# Return formatted number
return '{:.9f}'.format(number / 1E9).rstrip('0').rstrip('.')
# Main function
# Create context
context = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN)
# Get the tip height from the node
tipHeight = requests.post(SERVER + '/v2/foreign', json = {
'jsonrpc': '2.0',
'id': 1,
'method': 'get_tip',
'params': []
}).json()['result']['Ok']['height']
# Set file name
fileName = 'transaction history ' + str(datetime.now()).split('.')[0] + '.txt'
# Display start message
print('Scanning to block %d and writing the transaction history to \'%s\' for the wallet with the rewind hash %s.' % (tipHeight, fileName, REWIND_HASH.hex()))
# Initialize current outputs
currentOutputs = {}
# Open file
with open(fileName, 'w') as file:
# Initialize last percent time
lastPercentTime = time.time()
# Initialize mined amount
minedAmount = 0
# Initialized mined start block
minedStartBlock = 0
# Go through all blocks
for i in range(tipHeight + 1):
# Get block from the node
block = requests.post(SERVER + '/v2/foreign', json = {
'jsonrpc': '2.0',
'id': 1,
'method': 'get_block',
'params': [
i,
None,
None
]
}).json()['result']['Ok']
# Initialize spent amount
spentAmount = 0
# Initialize received amount
receivedAmount = 0
# Go through all inputs in the block
for input in block['inputs']:
# Check if input is a current outputs
if input in currentOutputs:
# Add input's amount to spent amount
spentAmount += currentOutputs[input]
# Write spent output message to file
file.write('Spent output %s containing %s Grin in block %d at %s\n' % (input, formatNumber(currentOutputs[input]), i, block['header']['timestamp']))
# Remove input from current outputs
del currentOutputs[input]
# Go through all outputs in the block
for output in block['outputs']:
# Get commit and proof from the output
commit = bytes.fromhex(output['commit'])
proof = bytes.fromhex(output['proof'])
# Get rewind nonce for the commit
rewindNonce = blake2b(REWIND_HASH, digest_size = 32, key = commit).digest()
# Check if the output belongs to the wallet
internalCommit = secp256k1_pedersen_commitment_parse(context, commit)
outputData = secp256k1_bulletproof_rangeproof_rewind(context, proof, 0, internalCommit, secp256k1_generator_const_h, rewindNonce, None)
if outputData is not None:
# Get amount from the output data
amount = outputData[0]
# Check if output is a coinbase reward
if output['output_type'] == 'Coinbase':
# Add output's amount to mined amount
minedAmount += amount
# Otherwise
else:
# Add output's amount to received amount
receivedAmount += amount
# Write received output message to file
file.write('Received %s output %s containing %s Grin in block %d at %s\n' % ('coinbase' if output['output_type'] == 'Coinbase' else 'transaction', output['commit'], formatNumber(amount), i, block['header']['timestamp']))
# Add output to current outputs
currentOutputs[output['commit']] = amount
# Check if amount was spent or received
if spentAmount != 0 or receivedAmount != 0:
# Check if mined amount exists
if minedAmount != 0:
# Show mined amount message
print('You mined %s Grin from block %d to block %d.' % (formatNumber(minedAmount), minedStartBlock, i))
# Reset mined amount
minedAmount = 0
# Reset mined start block
minedStartBlock = i + 1
# Check if more was spent than received
if spentAmount > receivedAmount:
# Show spent and received amount message
print('You spent %s Grin and received %s Grin in block %d at %s. You probably sent someone %s Grin including fees.' % (formatNumber(spentAmount), formatNumber(receivedAmount), i, block['header']['timestamp'], formatNumber(spentAmount - receivedAmount)))
# Otherwise
else:
# Show spent and received amount message
print('You spent %s Grin and received %s Grin in block %d at %s.' % (formatNumber(spentAmount), formatNumber(receivedAmount), i, block['header']['timestamp']))
# Get current time
currentTime = time.time()
# Check if a minute has passed since the last percent time
if currentTime - lastPercentTime >= 60:
# Show percent complete message
print('%s%% complete.' % ('{:.9f}'.format(i / tipHeight * 100).rstrip('0').rstrip('.')))
# Update last percent time
lastPercentTime = currentTime
# Check if mined amount exists
if minedAmount != 0:
# Show mined amount message
print('You mined %s Grin from block %d to block %d.' % (formatNumber(minedAmount), minedStartBlock, tipHeight))
# Show done scanning message
print('Done scanning.')
# Initialize balance
balance = 0
# Go through all current outputs
for output in currentOutputs:
# Add output's amount to balance
balance += currentOutputs[output]
# Show current balance message
print('Current balance is %s Grin.' % (formatNumber(balance)))
# Destroy the context
secp256k1_context_destroy(context)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment