Skip to content

Commit e7b98f9

Browse files
committed
add comments on code
1 parent c41ce22 commit e7b98f9

File tree

1 file changed

+38
-27
lines changed

1 file changed

+38
-27
lines changed

otsserver/backup.py

+38-27
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import time
1919
from urllib.parse import urlparse, urljoin
2020

21-
PAGING = 1000
22-
SLEEP_SECS = 600
21+
PAGING = 1000 # Number of commitments per chunk
22+
SLEEP_SECS = 600 # Once the backup is synced this is the polling interval to check for new chunks
2323

2424

2525
class Backup:
@@ -29,27 +29,36 @@ def __init__(self, journal, calendar, cache_path):
2929
self.cache_path = cache_path
3030
os.makedirs(cache_path, exist_ok=True)
3131

32+
# Return the bytes of the chunk
3233
def __getitem__(self, chunk):
34+
35+
# We use a disk cache, creating a chunk of 1000 commitments is a quite expensive operation of about 10s.
36+
# The server isn't blocked in the meantime but this could be used by an attacker to degrade calendar performance
37+
# Moreover is not recommended to set up an HTTP cache more than one year (see RFC 2616), thus, a disk cache is
38+
# mandatory.
3339
cached_kv_bytes = self.read_disk_cache(chunk)
3440
if cached_kv_bytes is not None:
3541
return cached_kv_bytes
3642

3743
backup_map = {}
3844
start = chunk*PAGING
3945
end = start+PAGING
40-
for i in range(start, end)[::-1]: # iterate in reverse to fail fast
46+
47+
# Iterate in reverse to fail fast if this chunk is not complete, a chunk is considered complete if all relative
48+
# 1000 commitments are complete. Which means a tx with more of 6 confirmations timestamp them
49+
for i in range(start, end)[::-1]:
4150
try:
4251
current = self.journal[i]
43-
# print(str(i) +":"+b2x(journal[i]))
4452
current_el = self.calendar[current]
45-
# print("\t"+str(current_el))
4653
self.__create_kv_map(current_el, current_el.msg, backup_map)
4754
except KeyError:
55+
# according to https://docs.python.org/3/library/exceptions.html#IndexError IndexError is the more
56+
# appropriate exception for this case
4857
raise IndexError
4958
if i % 100 == 0:
50-
logging.info(str(i) + ":" + b2x(self.journal[i]))
59+
logging.debug("Got commitment " + str(i) + ":" + b2x(self.journal[i]))
5160

52-
logging.info("map len " + str(len(backup_map)) + " start:" + str(start) + " end:" + str(end))
61+
logging.debug("map len " + str(len(backup_map)) + " start:" + str(start) + " end:" + str(end))
5362
kv_bytes = self.__kv_map_to_bytes(backup_map)
5463
self.write_disk_cache(chunk, kv_bytes)
5564

@@ -92,6 +101,7 @@ def __create_kv_map(ts, msg, kv_map):
92101
@staticmethod
93102
def __kv_map_to_bytes(kv_map):
94103
ctx = BytesSerializationContext()
104+
# Sorting the map elements to create chunks deterministically, but this is not mandatory for importing the chunk
95105
for key, value in sorted(kv_map.items()):
96106
ctx.write_varuint(len(key))
97107
ctx.write_bytes(key)
@@ -101,8 +111,11 @@ def __kv_map_to_bytes(kv_map):
101111
return ctx.getbytes()
102112

103113
def read_disk_cache(self, chunk):
114+
# For the disk cache we are using 6 digits file name which will support a total of 1 billion commitments,
115+
# because every chunk contain 1000 commitments. Supposing 1 commitment per second this could last for 32 years
116+
# which appear to be ok for this version
104117
chunk_str = "{0:0>6}".format(chunk)
105-
chunk_path = chunk_str[0:3]
118+
chunk_path = chunk_str[0:3] # we create a path to avoid creating more than 1000 files per directory
106119

107120
try:
108121
cache_file = self.cache_path + '/' + chunk_path + '/' + chunk_str
@@ -120,7 +133,8 @@ def write_disk_cache(self, chunk, bytes):
120133
with open(cache_file, 'wb') as fd:
121134
fd.write(bytes)
122135

123-
136+
# The following is a shrinked version of the standard calendar http server, it only support the '/timestamp' endpoint
137+
# This way the backup server could serve request in place of the calendar serve which is backupping
124138
class RPCRequestHandler(http.server.BaseHTTPRequestHandler):
125139

126140
def do_GET(self):
@@ -197,6 +211,9 @@ def serve_forever(self):
197211
super().serve_forever()
198212

199213

214+
# This is the thread responsible for asking the chunks to the running calendar and import them in the db.
215+
# The main script allow to launch 1 thread of this for every calendar to backup, thus a backup server could
216+
# theoretically serve timestamp in place of every calendar server which supports this incremental live backup mechanism
200217
class AskBackup(threading.Thread):
201218

202219
def __init__(self, db, calendar_url, base_path):
@@ -208,36 +225,33 @@ def __init__(self, db, calendar_url, base_path):
208225
super().__init__(target=self.loop)
209226

210227
def loop(self):
211-
print("Starting loop for %s" % self.calendar_url)
228+
logging.info("Starting loop for %s" % self.calendar_url)
212229

213230
try:
214231
with open(self.up_to_path, 'r') as up_to_fd:
215232
last_known = int(up_to_fd.read().strip())
216233
except FileNotFoundError as exp:
217234
last_known = -1
218-
print("Checking calendar " + str(self.calendar_url) + ", last_known commitment:" + str(last_known))
235+
logging.info("Checking calendar " + str(self.calendar_url) + ", last_known commitment:" + str(last_known))
219236

220237
while True:
221238
start_time = time.time()
222239
backup_url = urljoin(self.calendar_url, "/experimental/backup/%d" % (last_known + 1))
223-
print(str(backup_url))
240+
logging.debug("Asking " + str(backup_url))
224241
try:
225242
r = requests.get(backup_url)
226243
except Exception as err:
227-
print("Exception asking " + str(backup_url) + " message " + str(err))
244+
logging.error("Exception asking " + str(backup_url) + " message " + str(err))
228245
break
229246

230247
if r.status_code == 404:
231-
print("%s not found, sleeping for %s seconds" % (backup_url, SLEEP_SECS) )
248+
logging.info("%s not found, sleeping for %s seconds" % (backup_url, SLEEP_SECS) )
232249
time.sleep(SLEEP_SECS)
233250
continue
234251

235-
# print(r.raw.read(10))
236252
kv_map = Backup.bytes_to_kv_map(r.content)
237-
# print(str(map))
238253
attestations = {}
239254
ops = {}
240-
print("kv_maps elements " + str(len(kv_map)))
241255
for key, value in kv_map.items():
242256
# print("--- key=" + b2x(key) + " value=" + b2x(value))
243257
ctx = BytesDeserializationContext(value)
@@ -252,31 +266,28 @@ def loop(self):
252266

253267
proxy = bitcoin.rpc.Proxy()
254268

255-
# verify all bitcoin attestation are valid
256-
print("total attestations: " + str(len(attestations)))
269+
# Verify all bitcoin attestation are valid
270+
logging.debug("Total attestations: " + str(len(attestations)))
257271
for key, attestation in attestations.items():
258272
if attestation.__class__ == BitcoinBlockHeaderAttestation:
259273
blockhash = proxy.getblockhash(attestation.height)
260274
block_header = proxy.getblockheader(blockhash)
275+
# the following raise an exception and block computation if the attestation does not verify
261276
attested_time = attestation.verify_against_blockheader(key, block_header)
262-
print("verifying " + b2x(key) + " result " + str(attested_time))
277+
logging.debug("Verifying " + b2x(key) + " result " + str(attested_time))
263278

264279
# verify all ops connects to an attestation
265-
print("total ops: " + str(len(ops)))
280+
logging.debug("Total ops: " + str(len(ops)))
266281
for key, op in ops.items():
267-
268-
# print("key " + b2x(key) + " op " + str(op))
269282
current_key = key
270283
current_op = op
271284
while True:
272285
next_key = current_op(current_key)
273-
# print("next_key " + b2x(next_key))
274286
if next_key in ops:
275287
current_key = next_key
276288
current_op = ops[next_key]
277289
else:
278290
break
279-
# print("maps to " + b2x(next_key))
280291
assert next_key in attestations
281292

282293
batch = leveldb.WriteBatch()
@@ -289,11 +300,11 @@ def loop(self):
289300
with open(self.up_to_path, 'w') as up_to_fd:
290301
up_to_fd.write('%d\n' % last_known)
291302
except FileNotFoundError as exp:
292-
print(str(exp))
303+
logging.error(str(exp))
293304
break
294305

295306
elapsed_time = time.time() - start_time
296-
print("Took %ds" % elapsed_time)
307+
logging.info("Took %ds for %s" % (elapsed_time, str(backup_url)))
297308

298309

299310

0 commit comments

Comments
 (0)