zeroname_updater.py 8.4 KB

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