zeroname_updater.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import time
  2. import json
  3. import os
  4. import sys
  5. import re
  6. import socket
  7. from subprocess import call
  8. from bitcoinrpc.authproxy import AuthServiceProxy
  9. def publish():
  10. print "* Signing and Publishing..."
  11. call(" ".join(command_sign_publish), shell=True)
  12. def processNameOp(domain, value, test=False):
  13. if not value.strip().startswith("{"):
  14. return False
  15. try:
  16. data = json.loads(value)
  17. except Exception, err:
  18. print "Json load error: %s" % err
  19. return False
  20. if "zeronet" not in data and "map" not in data:
  21. # Namecoin standard use {"map": { "blog": {"zeronet": "1D..."} }}
  22. print "No zeronet and no map in ", data.keys()
  23. return False
  24. if "map" in data:
  25. # If subdomains using the Namecoin standard is present, just re-write in the Zeronet way
  26. # and call the function again
  27. data_map = data["map"]
  28. new_value = {}
  29. for subdomain in data_map:
  30. if "zeronet" in data_map[subdomain]:
  31. new_value[subdomain] = data_map[subdomain]["zeronet"]
  32. if "zeronet" in data and isinstance(data["zeronet"], basestring):
  33. # {
  34. # "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9",
  35. # ....
  36. # }
  37. new_value[""] = data["zeronet"]
  38. if len(new_value) > 0:
  39. return processNameOp(domain, json.dumps({"zeronet": new_value}), test)
  40. else:
  41. return False
  42. if "zeronet" in data and isinstance(data["zeronet"], basestring):
  43. # {
  44. # "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9"
  45. # } is valid
  46. return processNameOp(domain, json.dumps({"zeronet": { "": data["zeronet"]}}), test)
  47. if not isinstance(data["zeronet"], dict):
  48. print "Not dict: ", data["zeronet"]
  49. return False
  50. if not re.match("^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$", domain):
  51. print "Invalid domain: ", domain
  52. return False
  53. if test:
  54. return True
  55. if "slave" in sys.argv:
  56. print "Waiting for master update arrive"
  57. time.sleep(30) # Wait 30 sec to allow master updater
  58. # Note: Requires the file data/names.json to exist and contain "{}" to work
  59. names_raw = open(names_path, "rb").read()
  60. names = json.loads(names_raw)
  61. for subdomain, address in data["zeronet"].items():
  62. subdomain = subdomain.lower()
  63. address = re.sub("[^A-Za-z0-9]", "", address)
  64. print subdomain, domain, "->", address
  65. if subdomain:
  66. if re.match("^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$", subdomain):
  67. names["%s.%s.bit" % (subdomain, domain)] = address
  68. else:
  69. print "Invalid subdomain:", domain, subdomain
  70. else:
  71. names["%s.bit" % domain] = address
  72. new_names_raw = json.dumps(names, indent=2, sort_keys=True)
  73. if new_names_raw != names_raw:
  74. open(names_path, "wb").write(new_names_raw)
  75. print "-", domain, "Changed"
  76. return True
  77. else:
  78. print "-", domain, "Not changed"
  79. return False
  80. def processBlock(block_id, test=False):
  81. print "Processing block #%s..." % block_id
  82. s = time.time()
  83. block_hash = rpc.getblockhash(block_id)
  84. block = rpc.getblock(block_hash)
  85. print "Checking %s tx" % len(block["tx"])
  86. updated = 0
  87. for tx in block["tx"]:
  88. try:
  89. transaction = rpc.getrawtransaction(tx, 1)
  90. for vout in transaction.get("vout", []):
  91. if "scriptPubKey" in vout and "nameOp" in vout["scriptPubKey"] and "name" in vout["scriptPubKey"]["nameOp"]:
  92. name_op = vout["scriptPubKey"]["nameOp"]
  93. updated += processNameOp(name_op["name"].replace("d/", ""), name_op["value"], test)
  94. except Exception, err:
  95. print "Error processing tx #%s %s" % (tx, err)
  96. print "Done in %.3fs (updated %s)." % (time.time() - s, updated)
  97. return updated
  98. # Connecting to RPC
  99. def initRpc(config):
  100. """Initialize Namecoin RPC"""
  101. rpc_data = {
  102. 'connect': '127.0.0.1',
  103. 'port': '8336',
  104. 'user': 'PLACEHOLDER',
  105. 'password': 'PLACEHOLDER',
  106. 'clienttimeout': '900'
  107. }
  108. try:
  109. fptr = open(config, 'r')
  110. lines = fptr.readlines()
  111. fptr.close()
  112. except:
  113. return None # Or take some other appropriate action
  114. for line in lines:
  115. if not line.startswith('rpc'):
  116. continue
  117. key_val = line.split(None, 1)[0]
  118. (key, val) = key_val.split('=', 1)
  119. if not key or not val:
  120. continue
  121. rpc_data[key[3:]] = val
  122. url = 'http://%(user)s:%(password)s@%(connect)s:%(port)s' % rpc_data
  123. return url, int(rpc_data['clienttimeout'])
  124. # Loading config...
  125. # Check whether platform is on windows or linux
  126. # On linux namecoin is installed under ~/.namecoin, while on on windows it is in %appdata%/Namecoin
  127. if sys.platform == "win32":
  128. namecoin_location = os.getenv('APPDATA') + "/Namecoin/"
  129. else:
  130. namecoin_location = os.path.expanduser("~/.namecoin/")
  131. config_path = namecoin_location + 'zeroname_config.json'
  132. if not os.path.isfile(config_path): # Create sample config
  133. open(config_path, "w").write(
  134. json.dumps({'site': 'site', 'zeronet_path': '/home/zeronet', 'privatekey': '', 'lastprocessed': 223910}, indent=2)
  135. )
  136. print "* Example config written to %s" % config_path
  137. sys.exit(0)
  138. config = json.load(open(config_path))
  139. names_path = "%s/data/%s/data/names.json" % (config["zeronet_path"], config["site"])
  140. os.chdir(config["zeronet_path"]) # Change working dir - tells script where Zeronet install is.
  141. # Parameters to sign and publish
  142. command_sign_publish = [sys.executable, "zeronet.py", "siteSign", config["site"], config["privatekey"], "--publish"]
  143. if sys.platform == 'win32':
  144. command_sign_publish = ['"%s"' % param for param in command_sign_publish]
  145. # Initialize rpc connection
  146. rpc_auth, rpc_timeout = initRpc(namecoin_location + "namecoin.conf")
  147. rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)
  148. node_version = rpc.getnetworkinfo()['version']
  149. while 1:
  150. try:
  151. time.sleep(1)
  152. if node_version < 160000 :
  153. last_block = int(rpc.getinfo()["blocks"])
  154. else:
  155. last_block = int(rpc.getblockchaininfo()["blocks"])
  156. break # Connection succeeded
  157. except socket.timeout: # Timeout
  158. print ".",
  159. sys.stdout.flush()
  160. except Exception, err:
  161. print "Exception", err.__class__, err
  162. time.sleep(5)
  163. rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)
  164. if not config["lastprocessed"]: # First startup: Start processing from last block
  165. config["lastprocessed"] = last_block
  166. print "- Testing domain parsing..."
  167. assert processBlock(223911, test=True) # Testing zeronetwork.bit
  168. assert processBlock(227052, test=True) # Testing brainwallets.bit
  169. assert not processBlock(236824, test=True) # Utf8 domain name (invalid should skip)
  170. assert not processBlock(236752, test=True) # Uppercase domain (invalid should skip)
  171. assert processBlock(236870, test=True) # Encoded domain (should pass)
  172. assert processBlock(438317, test=True) # Testing namecoin standard artifaxradio.bit (should pass)
  173. # sys.exit(0)
  174. print "- Parsing skipped blocks..."
  175. should_publish = False
  176. for block_id in range(config["lastprocessed"], last_block + 1):
  177. if processBlock(block_id):
  178. should_publish = True
  179. config["lastprocessed"] = last_block
  180. if should_publish:
  181. publish()
  182. while 1:
  183. print "- Waiting for new block"
  184. sys.stdout.flush()
  185. while 1:
  186. try:
  187. time.sleep(1)
  188. if node_version < 160000 :
  189. rpc.waitforblock()
  190. else:
  191. rpc.waitfornewblock()
  192. print "Found"
  193. break # Block found
  194. except socket.timeout: # Timeout
  195. print ".",
  196. sys.stdout.flush()
  197. except Exception, err:
  198. print "Exception", err.__class__, err
  199. time.sleep(5)
  200. rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)
  201. if node_version < 160000 :
  202. last_block = int(rpc.getinfo()["blocks"])
  203. else:
  204. last_block = int(rpc.getblockchaininfo()["blocks"])
  205. should_publish = False
  206. for block_id in range(config["lastprocessed"] + 1, last_block + 1):
  207. if processBlock(block_id):
  208. should_publish = True
  209. config["lastprocessed"] = last_block
  210. open(config_path, "w").write(json.dumps(config, indent=2))
  211. if should_publish:
  212. publish()