Skip to content

Commit e2ebe52

Browse files
committed
updates to support dockerization
1 parent 67399fe commit e2ebe52

4 files changed

Lines changed: 92 additions & 13 deletions

File tree

backend/app/__init__.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
env_path = Path(__file__).parent.parent / '.env'
99
load_dotenv(dotenv_path=env_path)
1010

11-
from flask import Flask, jsonify, request
11+
from flask import Flask, jsonify, request, Response
1212
from flask_cors import CORS
13+
import json
1314

1415
from backend.blockchain.blockchain import Blockchain
1516
from backend.wallet.wallet import Wallet
@@ -19,6 +20,18 @@
1920

2021
app = Flask(__name__)
2122
CORS(app, resources={ r'/*': { 'origins': 'http://localhost:3000' } })
23+
24+
def json_response(data, status=200):
25+
"""
26+
Create a JSON response that preserves large integers.
27+
Flask's default jsonify converts large ints to floats, which breaks
28+
cryptographic signatures. This function ensures integers are preserved.
29+
"""
30+
return Response(
31+
json.dumps(data, separators=(',', ':')),
32+
status=status,
33+
mimetype='application/json'
34+
)
2235
blockchain = Blockchain()
2336
wallet = Wallet(blockchain)
2437
transaction_pool = TransactionPool()
@@ -30,7 +43,7 @@ def route_default():
3043

3144
@app.route('/blockchain')
3245
def route_blockchain():
33-
return jsonify(blockchain.to_json())
46+
return json_response(blockchain.to_json())
3447

3548
@app.route('/blockchain/range')
3649
def route_blockchain_range():
@@ -53,7 +66,7 @@ def route_blockchain_mine():
5366
pubsub.broadcast_block(block)
5467
transaction_pool.clear_blockchain_transactions(blockchain)
5568

56-
return jsonify(block.to_json())
69+
return json_response(block.to_json())
5770

5871
@app.route('/wallet/transact', methods=['POST'])
5972
def route_wallet_transact():
@@ -102,7 +115,9 @@ def route_transactions():
102115
if os.environ.get('PEER') == 'True':
103116
PORT = random.randint(5051, 6000)
104117

105-
result = requests.get(f'http://localhost:{ROOT_PORT}/blockchain')
118+
# In Docker, use service name instead of localhost
119+
ROOT_HOST = os.environ.get('ROOT_BACKEND_HOST', 'localhost')
120+
result = requests.get(f'http://{ROOT_HOST}:{ROOT_PORT}/blockchain')
106121
result_blockchain = Blockchain.from_json(result.json())
107122

108123
try:
@@ -119,9 +134,10 @@ def route_transactions():
119134
])
120135

121136
for i in range(3):
122-
transaction_pool.set_transaction(
123-
Transaction(Wallet(), Wallet().address, random.randint(2, 50))
124-
)
137+
transaction = Transaction(Wallet(), Wallet().address, random.randint(2, 50))
138+
pubsub.broadcast_transaction(transaction)
139+
transaction_pool.set_transaction(transaction)
125140

126-
app.run(port=PORT)
141+
if __name__ == "__main__":
142+
app.run(host="0.0.0.0", port=PORT, debug=True)
127143

backend/pubsub.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import os
22
import time
3+
import requests
34

45
from pubnub.pubnub import PubNub
56
from pubnub.pnconfiguration import PNConfiguration
67
from pubnub.callbacks import SubscribeCallback
78

89
from backend.blockchain.block import Block
10+
from backend.blockchain.blockchain import Blockchain
911
from backend.wallet.transaction import Transaction
1012

1113
pnconfig = PNConfiguration()
@@ -42,10 +44,38 @@ def message(self, pubnub, message_object):
4244
print('\n -- Successfully replaced the local chain')
4345
except Exception as e:
4446
print(f'\n -- Did not replace chain: {e}')
47+
48+
# If we can't validate the block, try to sync the full blockchain
49+
# This handles cases where we're missing previous blocks
50+
self.sync_blockchain()
51+
4552
elif message_object.channel == CHANNELS['TRANSACTION']:
4653
transaction = Transaction.from_json(message_object.message)
4754
self.transaction_pool.set_transaction(transaction)
4855
print('\n -- Set the new transaction in the transaction pool')
56+
57+
def sync_blockchain(self):
58+
"""
59+
Synchronize the local blockchain with the root node.
60+
This is called when we receive a block we can't validate.
61+
"""
62+
try:
63+
# Get the root backend host (main node)
64+
root_host = os.environ.get('ROOT_BACKEND_HOST', 'localhost')
65+
root_port = os.environ.get('ROOT_PORT', '5050')
66+
67+
print(f'\n -- Attempting to sync blockchain from {root_host}:{root_port}')
68+
69+
# Request the full blockchain from the root node
70+
response = requests.get(f'http://{root_host}:{root_port}/blockchain')
71+
result_blockchain = Blockchain.from_json(response.json())
72+
73+
# Replace our local chain with the synchronized chain
74+
self.blockchain.replace_chain(result_blockchain.chain)
75+
76+
print(f'\n -- Successfully synchronized! Chain length: {len(self.blockchain.chain)}')
77+
except Exception as e:
78+
print(f'\n -- Could not synchronize blockchain: {e}')
4979

5080
class PubSub():
5181
"""
@@ -61,7 +91,11 @@ def publish(self, channel, message):
6191
"""
6292
Publish the message object to the channel.
6393
"""
64-
self.pubnub.publish().channel(channel).message(message).sync()
94+
try:
95+
result = self.pubnub.publish().channel(channel).message(message).sync()
96+
print(f'\n-- Published to {channel}: {result.status.is_error()}')
97+
except Exception as e:
98+
print(f'\n-- Error publishing to {channel}: {e}')
6599

66100
def broadcast_block(self, block):
67101
"""

backend/wallet/transaction.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,48 @@ def update(self, sender_wallet, recipient, amount):
6969

7070
self.input = self.create_input(sender_wallet, self.output)
7171

72+
# TODO: consider making my own python library for this, to clean it up for the student.
73+
# That, or clone from the course repo.
7274
def to_json(self):
7375
"""
7476
Serialize the transaction.
77+
Convert large signature integers to strings to prevent float conversion in JSON.
7578
"""
76-
return self.__dict__
79+
transaction_dict = self.__dict__.copy()
80+
81+
# If this transaction has a signature (not a mining reward), convert it to strings
82+
if self.input != None and isinstance(self.input, dict) and 'signature' in self.input:
83+
transaction_dict = {
84+
'id': self.id,
85+
'output': self.output.copy(),
86+
'input': self.input.copy()
87+
}
88+
# Convert signature tuple/list to string representations
89+
sig = self.input['signature']
90+
transaction_dict['input']['signature'] = [str(sig[0]), str(sig[1])]
91+
92+
return transaction_dict
7793

7894
@staticmethod
7995
def from_json(transaction_json):
8096
"""
8197
Deserialize a transaction's json representation back into a
82-
Transaction instance
98+
Transaction instance.
99+
Convert signature strings back to integers.
83100
"""
84-
return Transaction(**transaction_json)
101+
# Make a copy to avoid mutating the original
102+
transaction_data = transaction_json.copy()
103+
104+
# If there's a signature, convert strings back to integers
105+
if 'input' in transaction_data and transaction_data['input'] is not None:
106+
if isinstance(transaction_data['input'], dict) and 'signature' in transaction_data['input']:
107+
sig = transaction_data['input']['signature']
108+
# Convert string representations back to integers
109+
if isinstance(sig[0], str):
110+
transaction_data['input'] = transaction_data['input'].copy()
111+
transaction_data['input']['signature'] = [int(sig[0]), int(sig[1])]
112+
113+
return Transaction(**transaction_data)
85114

86115
@staticmethod
87116
def is_valid_transaction(transaction):

frontend/src/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const API_BASE_URL = 'http://localhost:5050';
1+
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:5050';
22
const NANOSECONDS_PY = 1;
33
const MICROSECONDS_PY = 1000 * NANOSECONDS_PY;
44
const MILLISECONDS_PY = 1000 * MICROSECONDS_PY;

0 commit comments

Comments
 (0)