master
Raw Download raw file
  1from __future__ import print_function
  2import pygit2
  3#   brew install libgit2
  4#   pip install pygit2
  5import tempfile # import mkstemp
  6import os # import remove, close, path, stat, chmod, urandom, mkdir, devnull
  7import shutil # import move, rmtree
  8import hashlib # import sha1
  9import time # import time
 10import subprocess # import call, STDOUT
 11import sys # import stderr 
 12from config import * # global variables
 13
 14def init_repo(repo_dir, remote_uri):
 15  """initialize (destructive) a repo into repo_dir from remote_uri"""
 16  shutil.rmtree(repo_dir, ignore_errors=True)
 17  os.mkdir(repo_dir)
 18  # pygit2 doesn't like the way stripe's git repo is setup
 19  # I don't care enough to actually figure out why...
 20  # any feedback on this would be appreciated :)
 21  subprocess.call(["git", "clone", remote_uri, repo_dir], 
 22                  stdout=open(os.devnull,'w'), 
 23                  stderr=subprocess.STDOUT)
 24  print("gitchain init complete", file=sys.stderr)
 25  return pygit2.init_repository(repo_dir)
 26
 27def update_ledger(ledger_path, username):
 28  """add one gitcoin to the username in the ledger file at ledger_path"""
 29  coin_added = False
 30  ledger = open(ledger_path, "r")
 31  fh, abs_path = tempfile.mkstemp()
 32  new_ledger = open(abs_path, "w")
 33  for line in ledger:
 34    if username in line:
 35      new_ledger.write(username+": "+str(int(line.split(": ")[1])+1)+"\n")
 36      coin_added = True
 37    else:
 38      new_ledger.write(line)
 39  if not coin_added:
 40    new_ledger.write(username+": 1\n")
 41  new_ledger.close()
 42  os.close(fh)
 43  ledger.close()
 44  os.remove(ledger_path)
 45  shutil.move(abs_path, ledger_path)
 46  os.chmod(ledger_path, 0644)
 47
 48def fastforward_repo(repo, repo_dir, remote_branch):
 49  """reset repository at repo_dir and return the resulting pygit2 repo object"""
 50  # see previous about fetching issues with pygit2
 51  subprocess.call(["git", "fetch", "--all"], 
 52                  cwd=repo_dir, 
 53                  stdout=open(os.devnull,'w'), 
 54                  stderr=subprocess.STDOUT)
 55  fastforward = False
 56  # get the oid of the remote branch, if != current head fastforward
 57  branch = repo.lookup_branch(remote_branch, pygit2.GIT_BRANCH_REMOTE)
 58  if branch.target.hex != repo.head.target.hex:
 59    print("fast forwarding gitchain", file=sys.stderr)
 60    # merge to remote, if conflicts occur it is because a genesis event 
 61    try: merge_result = repo.merge(repo.get(branch.target.hex).oid)
 62    except pygit2.GitError:
 63      print("gitchain had a genesis event: fetching!", file=sys.stderr)
 64      return init_repo(repo_dir, repo.remotes[0].url), True
 65    if merge_result.is_fastforward:
 66      repo.reset(merge_result.fastforward_oid, pygit2.GIT_RESET_HARD)
 67      if (branch.target.hex 
 68          == merge_result.fastforward_oid.hex 
 69          == repo.head.target.hex):
 70        fastforward = True
 71      else:
 72        raise Exception("git reset failed!")
 73    else:
 74      raise Exception("git merge failed!")
 75  else:
 76    print("no gitchian updates...", file=sys.stderr)
 77  return repo, fastforward
 78
 79def tree_add_to_repo(repo, repo_dir, filename):
 80  """
 81  add filename to repository and return the repo and the tree oid
 82  unfortunately this broke on commit, so instead see index_add_to_repo
 83  """
 84  oid = repo.create_blob_fromworkdir(filename)
 85  treebuilder = repo.TreeBuilder()
 86  treebuilder.insert(filename, 
 87                     oid, 
 88                     os.stat(os.path.join(repo_dir,filename)).st_mode)
 89  tree_oid = treebuilder.write()
 90  return repo, tree_oid
 91
 92def index_add_to_repo(repo, repo_dir, filename):
 93  """add filename to repository and return the repo and the tree oid"""
 94  repo.index.read()
 95  repo.index.add(filename)
 96  return repo, repo.index.write_tree() 
 97
 98def gen_commit_context(repo, tree, username, email, time, msg_len):
 99  """return the sha1 context lacking only the message portion of the commit"""
100  head = repo.head.target.hex
101  body = ("tree "+tree+"\n"
102          "parent "+head+"\n"
103          "author "+username+" <"+email+"> "+str(int(time))+" +0000\n"  
104          "committer "+username+" <"+email+"> "+str(int(time))+" +0000\n\n")
105  s = hashlib.sha1()
106  s.update("commit %u\0" % (len(body)+msg_len))
107  s.update(body)
108  return s
109
110def gen_commit(repo, tree, username, email, time, message):
111  """ useless function that creates the signature and commit """
112  sig = pygit2.Signature(name=username, email=email, time=time)
113  head = repo.head.target.hex
114  return repo.create_commit(repo.head.name, sig, sig, message, tree, [head]) 
115
116def proof_of_work(s, msg_len, difficulty, timeout):
117  # TODO: generalize and zmq this work out
118  """return message, digest, success""" 
119  freq = 168384*2
120  start = time.time()
121  worker_time = time.time()
122  count = 2
123  while True:
124    count += 1
125    message = os.urandom(msg_len/2).encode('hex') 
126    tmp_s = s.copy()
127    tmp_s.update(message)
128    if tmp_s.hexdigest() < difficulty: 
129      return message, tmp_s.hexdigest(), True
130    if (count % freq) == 1: 
131      hashrate = (((1/(time.time()-start))*freq)/1024)
132      print("~ %0.3f Kh/s" % hashrate, end='\n', file=sys.stderr)
133      start = time.time()
134    if (time.time()-worker_time) > timeout:
135      return "", "", False
136
137if __name__ == "__main__":
138  repo = init_repo(repo_dir=REPO_DIR, remote_uri=REMOTE_URI)
139  commit_time = time.time()
140
141  # add gitcoin to the repository and start commit sha1
142  difficulty = open(DIFFICULTY_PATH,"r").read().split()[0]
143  update_ledger(ledger_path=LEDGER_PATH, username=USERNAME)
144  repo, tree = index_add_to_repo(repo=repo, 
145                                 repo_dir=REPO_DIR, 
146                                 filename=LEDGER_FILENAME)
147  s = gen_commit_context(repo=repo, 
148                         tree=tree.hex,
149                         username=USERNAME, 
150                         email=EMAIL, 
151                         time=commit_time, 
152                         msg_len=MESSAGE_LENGTH)
153  
154  while True:
155  # mining loop
156    message, digest, success = proof_of_work(s=s,
157                                             msg_len=MESSAGE_LENGTH, 
158                                             difficulty=difficulty,
159                                             timeout=GIT_POLL_INTERVAL)
160    repo, fastforward = fastforward_repo(repo=repo, 
161                                         repo_dir=REPO_DIR, 
162                                         remote_branch=REMOTE_BRANCH)
163    if success and not fastforward:
164      print("mined gitcoin! commit-hash:[%s]" % (message, digest),
165            file=sys.stderr)
166      break
167    elif fastforward:
168        # re-add our gitcoin to the reset --hard'd repository 
169        difficulty = open(DIFFICULTY_PATH,"r").read().split()[0]
170        update_ledger(ledger_path=LEDGER_PATH, username=USERNAME)
171        repo, tree = index_add_to_repo(repo=repo, 
172                                       repo_dir=REPO_DIR, 
173                                       filename=LEDGER_FILENAME)
174        s = gen_commit_context(repo=repo, 
175                               tree=tree.hex,
176                               username=USERNAME, 
177                               email=EMAIL, 
178                               time=commit_time, 
179                               msg_len=MESSAGE_LENGTH)
180  # end mining loop
181
182  c = gen_commit(repo=repo,
183                 tree=tree, 
184                 username=USERNAME, 
185                 email=EMAIL, 
186                 time=commit_time, 
187                 message=message)
188
189  # make sure we really have a winner 
190  s.update(message)
191  if c.hex != s.hexdigest():
192    raise Exception("commit hash mismatch!")
193
194  # again, making up for pygit2's shortcomings...
195  subprocess.call(["git", "push", "origin", "master"], 
196                  cwd=REPO_DIR, 
197                  stdout=open(os.devnull,'w'), 
198                  stderr=subprocess.STDOUT)
199  # maybe dont need separate dirs?
200  shutil.rmtree(REPO_DIR, ignore_errors=True)