text: import os
  from binascii import hexlify
  from Crypto.Hash import keccak
  import random
  
  def to_hex(s):
      return hexlify(s).decode('utf-8')
  
  memo = {}
  
  def sha3(x):
      if x not in memo:
          memo[x] = keccak.new(digest_bits=256, data=x).digest()
      return memo[x]
  
  def hash_to_int(h):
      o = 0
      for c in h:
          o = (o << 8) + c
      return o
  
  NOTARIES = 40
  BASE_TS_DIFF = 1
  SKIP_TS_DIFF = 6
  SAMPLE = 8
  MIN_SAMPLE = 7
  POWDIFF = 50 * NOTARIES
  SHARDS = 12
  
  def checkpow(work, nonce):
      # Discrete log PoW, lolz
      # Quadratic nonresidues only
      return pow(work, nonce, 65537) * POWDIFF < 65537 * 2 and pow(nonce, 32768, 65537) == 65536
  
  class Block():
      def __init__(self, parent, pownonce, ts):
          self.parent_hash = parent.hash if parent else (b'\x00' * 32)
          assert isinstance(self.parent_hash, bytes)
          self.hash = sha3(self.parent_hash + str(pownonce).encode('utf-8'))
          self.ts = ts
          if parent:
              assert checkpow(parent.pownonce, pownonce)
              assert self.ts >= parent.ts
          self.pownonce = pownonce
          self.number = 0 if parent is None else parent.number + 1
              
  
  genesis = Block(None, 59049, 0)
  
  class Node():
  
      def __init__(self, _id, network, sleepy=False, careless=False, ts=0):
          self.blocks = {
              genesis.hash: genesis
          }
          self.main_chain = [genesis.hash]
          self.timequeue = []
          self.parentqueue = {}
          self.children = {}
          self.ts = ts
          self.id = _id
          self.network = network
          self.used_parents = {}
          self.processed = {}
          self.sleepy = sleepy
          self.careless = careless
  
      def broadcast(self, x):
          if self.sleepy and self.ts:
              return
          self.network.broadcast(self, x)
          self.on_receive(x)
  
      def log(self, words, lvl=3, all=False):
          #if "Tick:" != words[:5] or self.id == 0:
          if (self.id == 0 or all) and lvl >= 2:
              print(self.id, words)
  
      def on_receive(self, obj, reprocess=False):
          if obj.hash in self.processed and not reprocess:
              return
          self.processed[obj.hash] = obj
          if isinstance(obj, Block):
              return self.on_receive_main_block(obj)
  
      def add_to_timequeue(self, obj):
          i = 0
          while i < len(self.timequeue) and self.timequeue[i].ts < obj.ts: 
              i += 1
          self.timequeue.insert(i, obj)
  
      def add_to_multiset(self, _set, k, v):
          if k not in _set:
              _set[k] = []
          _set[k].append(v)
  
      def change_head(self, chain, new_head):
          chain.extend([None] * (new_head.number + 1 - len(chain)))
          i, c = new_head.number, new_head.hash
          while c != chain[i]:
              chain[i] = c
              c = self.blocks[c].parent_hash
              i -= 1
          for i in range(len(chain)):
              assert self.blocks[chain[i]].number == i
              assert self.blocks[chain[i]].ts <= self.ts
  
      def process_children(self, h):
          if h in self.parentqueue:
              for b in self.parentqueue[h]:
                  self.on_receive(b, reprocess=True)
              del self.parentqueue[h]
  
      def have_ancestry(self, h):
          while h != genesis.hash:
              if h not in self.processed:
                  return False
              h = self.processed[h].parent_hash
          return True
  
      def is_notarized(self, b):
          return b.hash in self.children
  
      def on_receive_main_block(self, block):
          # Parent not yet received
          if block.parent_hash not in self.blocks:
              self.add_to_multiset(self.parentqueue, block.parent_hash, block)
              return None
          if block.ts > self.ts:
              self.add_to_timequeue(block)
              return None
          self.log("Processing main chain block %s" % to_hex(block.hash[:4]))
          self.blocks[block.hash] = block
          # Reorg the main chain if new head
          if block.number > self.blocks[self.main_chain[-1]].number:
              reorging = (block.parent_hash != self.main_chain[-1])
              self.change_head(self.main_chain, block)
          # Add child record
          self.add_to_multiset(self.children, block.parent_hash, block.hash)
          # Final steps
          self.process_children(block.hash)
          self.network.broadcast(self, block)
  
      def is_descendant(self, a, b):
          a, b = self.blocks[a], self.blocks[b]
          while b.number > a.number:
              b = self.blocks[b.parent_hash]
          return a.hash == b.hash
  
      def tick(self):
          self.ts += 0.1
          self.log("Tick: %.1f" % self.ts, lvl=1)
          # Process time queue
          while len(self.timequeue) and self.timequeue[0].ts <= self.ts:
              self.on_receive(self.timequeue.pop(0), reprocess=True)
          # Attempt to mine a main chain block
          pownonce = random.randrange(65537)
          mchead = self.blocks[self.main_chain[-1]]
          if checkpow(mchead.pownonce, pownonce):
              assert self.ts >= mchead.ts
              self.broadcast(Block(mchead, pownonce, self.ts))
  