Last active
June 11, 2023 23:11
-
-
Save NicolasFlamel1/27465c7ad426863c8ae4a76d2219cceb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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