Commit 31b34be

bryfry <bryon.fryer@gmail.com>
2014-01-26 23:39:48
cleaned up version
1 parent e91958e
config.py
@@ -0,0 +1,13 @@
+import os # import path, getcwd, urandom
+
+USERNAME = "user-zueglyh2"
+EMAIL = USERNAME+"@gitco.in"
+#REMOTE_URI = "lvl1-k61vtf8e@stripe-ctf.com:level1"
+REMOTE_URI = "lvl1-k61vtf8e@stripe-ctf.com:current-round"
+REMOTE_BRANCH = 'origin/master'
+REPO_DIR = os.path.join(os.getcwd(), '.cache-'+os.urandom(4).encode('hex'))
+LEDGER_FILENAME = "LEDGER.txt"
+LEDGER_PATH = os.path.join(REPO_DIR,LEDGER_FILENAME)
+DIFFICULTY_PATH = os.path.join(REPO_DIR,"difficulty.txt")
+MESSAGE_LENGTH = 16
+GIT_POLL_INTERVAL = 45
gitcoin.py
@@ -1,94 +1,197 @@
-#!/usr/bin/python
-
 from __future__ import print_function
 import pygit2
 #   brew install libgit2
 #   pip install pygit2
-from tempfile import mkstemp
-from os import remove, close, path, stat, chmod
-from shutil import move
-from hashlib import sha1
-from random import randint
-from time import time
-import sys # print to stderr
-
-def update_ledger(ledger_path):
+import tempfile # import mkstemp
+import os # import remove, close, path, stat, chmod, urandom, mkdir, devnull
+import shutil # import move, rmtree
+import hashlib # import sha1
+import time # import time
+import subprocess # import call, STDOUT
+import sys # import stderr 
+from config import * # global variables
+
+def init_repo(repo_dir, remote_uri):
+  """initialize (destructive) a repo into repo_dir from remote_uri"""
+  shutil.rmtree(repo_dir, ignore_errors=True)
+  os.mkdir(repo_dir)
+  # pygit2 doesn't like the way stripe's git repo is setup
+  # I don't care enough to actually figure out why...
+  # any feedback on this would be appreciated :)
+  subprocess.call(["git", "clone", remote_uri, repo_dir], 
+                  stdout=open(os.devnull,'w'), 
+                  stderr=subprocess.STDOUT)
+  print("gitchain init complete", file=sys.stderr)
+  return pygit2.init_repository(repo_dir)
+
+def update_ledger(ledger_path, username):
+  """add one gitcoin to the username in the ledger file at ledger_path"""
   coin_added = False
   ledger = open(ledger_path, "r")
-  fh, abs_path = mkstemp()
+  fh, abs_path = tempfile.mkstemp()
   new_ledger = open(abs_path, "w")
-
   for line in ledger:
     if username in line:
       new_ledger.write(username+": "+str(int(line.split(": ")[1])+1)+"\n")
       coin_added = True
     else:
       new_ledger.write(line)
-
   if not coin_added:
     new_ledger.write(username+": 1\n")
-
   new_ledger.close()
-  close(fh)
+  os.close(fh)
   ledger.close()
-  remove(ledger_path)
-  move(abs_path, ledger_path)
-  chmod(ledger_path, 0644)
-
-def add_to_repo(repo, repo_path, filename):
-  "add filename to repository located at path and return the tree object id"
-  blob = repo.create_blob_fromworkdir(filename)
+  os.remove(ledger_path)
+  shutil.move(abs_path, ledger_path)
+  os.chmod(ledger_path, 0644)
+
+def fastforward_repo(repo, repo_dir, remote_branch):
+  """reset repository at repo_dir and return the resulting pygit2 repo object"""
+  # see previous about fetching issues with pygit2
+  subprocess.call(["git", "fetch", "--all"], 
+                  cwd=repo_dir, 
+                  stdout=open(os.devnull,'w'), 
+                  stderr=subprocess.STDOUT)
+  fastforward = False
+  # get the oid of the remote branch, if != current head fastforward
+  branch = repo.lookup_branch(remote_branch, pygit2.GIT_BRANCH_REMOTE)
+  if branch.target.hex != repo.head.target.hex:
+    print("fast forwarding gitchain", file=sys.stderr)
+    # merge to remote, if conflicts occur it is because a genesis event 
+    try: merge_result = repo.merge(repo.get(branch.target.hex).oid)
+    except pygit2.GitError:
+      print("gitchain had a genesis event: fetching!", file=sys.stderr)
+      return init_repo(repo_dir, repo.remotes[0].url), True
+    if merge_result.is_fastforward:
+      repo.reset(merge_result.fastforward_oid, pygit2.GIT_RESET_HARD)
+      if (branch.target.hex 
+          == merge_result.fastforward_oid.hex 
+          == repo.head.target.hex):
+        fastforward = True
+      else:
+        raise Exception("git reset failed!")
+    else:
+      raise Exception("git merge failed!")
+  else:
+    print("no gitchian updates...", file=sys.stderr)
+  return repo, fastforward
+
+def tree_add_to_repo(repo, repo_dir, filename):
+  """
+  add filename to repository and return the repo and the tree oid
+  unfortunately this broke on commit, so instead see index_add_to_repo
+  """
+  oid = repo.create_blob_fromworkdir(filename)
   treebuilder = repo.TreeBuilder()
-  treebuilder.insert(filename, blob, stat(path.join(repo_path,filename)).st_mode)
+  treebuilder.insert(filename, 
+                     oid, 
+                     os.stat(os.path.join(repo_dir,filename)).st_mode)
   tree_oid = treebuilder.write()
-  return tree_oid
-
-def reset_repo(repo):
-  pass
-
-def githash(data):
-  s = sha1()
-  s.update("commit %u\0" % len(data))
-  s.update(data)
-  return s.hexdigest()
-
-def mine_gitcoin(repo, repo_path):
-  difficulty_path = path.join(repo_path,"difficulty.txt")
-  difficulty = open(difficulty_path,"r").read().split()[0]
-  sig = pygit2.Signature(name='gitcoinminer', email='git@coin.co', time=time())
-  tree = add_to_repo(repo, repo_path, ledger_filename)
-  hex_head = [] if repo.is_empty else [repo.head.target.hex]
-
-  count = randint(0, sys.maxint/2)
-  start = time()
+  return repo, tree_oid
+
+def index_add_to_repo(repo, repo_dir, filename):
+  """add filename to repository and return the repo and the tree oid"""
+  repo.index.read()
+  repo.index.add(filename)
+  return repo, repo.index.write_tree() 
+
+def gen_commit_context(repo, tree, username, email, time, msg_len):
+  """return the sha1 context lacking only the message portion of the commit"""
+  head = repo.head.target.hex
+  body = ("tree "+tree+"\n"
+          "parent "+head+"\n"
+          "author "+username+" <"+email+"> "+str(int(time))+" +0000\n"  
+          "committer "+username+" <"+email+"> "+str(int(time))+" +0000\n\n")
+  s = hashlib.sha1()
+  s.update("commit %u\0" % (len(body)+msg_len))
+  s.update(body)
+  return s
+
+def gen_commit(repo, tree, username, email, time, message):
+  """ useless function that creates the signature and commit """
+  sig = pygit2.Signature(name=username, email=email, time=time)
+  head = repo.head.target.hex
+  return repo.create_commit(repo.head.name, sig, sig, message, tree, [head]) 
+
+def proof_of_work(s, msg_len, difficulty, timeout):
+  # TODO: generalize and zmq this work out
+  """return message, digest, success""" 
+  freq = 168384
+  start = time.time()
+  worker_time = time.time()
+  count = 2
   while True:
-
-    nonce = str(count) #sha1(str(count)).hexdigest()
-    c = repo.create_commit(repo.head.name, sig, sig, nonce, tree, hex_head) 
-
     count += 1
-    if c.hex < difficulty: break
-    if (count % 1024) == 1: 
-      end = time()
-      print("%0.3f Kh/s ... %d" % (1/(end-start), count), end='\n', file=sys.stderr)
-      start = time()
-    break
-
-  print (c.hex)
-
-username = "user-zueglyh2"
-repo_path = "/Users/bryon/r-bryfry/level1/"
-
-ledger_filename = "LEDGER.txt"
-ledger_path = path.join(repo_path,ledger_filename)
-
-repo = pygit2.Repository(repo_path)
-reset_repo(repo)
-update_ledger(ledger_path)
-mine_gitcoin(repo, repo_path)
-
-
-#write to disk
-#repo.index.read()
-#repo.index.add(filename)
-#repo.index.write()
+    message = os.urandom(msg_len/2).encode('hex') 
+    tmp_s = s.copy()
+    tmp_s.update(message)
+    if tmp_s.hexdigest() < difficulty: 
+      return message, tmp_s.hexdigest(), True
+    if (count % freq) == 1: 
+      hashrate = (((1/(time.time()-start))*freq)/1024)
+      print("~ %0.3f Kh/s" % hashrate, end='\n', file=sys.stderr)
+      start = time.time()
+    if (time.time()-worker_time) > timeout:
+      return "", "", False
+
+if __name__ == "__main__":
+  repo = init_repo(repo_dir=REPO_DIR, remote_uri=REMOTE_URI)
+  commit_time = time.time()
+
+  # add gitcoin to the repository and start commit sha1
+  difficulty = open(DIFFICULTY_PATH,"r").read().split()[0]
+  update_ledger(ledger_path=LEDGER_PATH, username=USERNAME)
+  repo, tree = index_add_to_repo(repo=repo, 
+                                 repo_dir=REPO_DIR, 
+                                 filename=LEDGER_FILENAME)
+  s = gen_commit_context(repo=repo, 
+                         tree=tree.hex,
+                         username=USERNAME, 
+                         email=EMAIL, 
+                         time=commit_time, 
+                         msg_len=MESSAGE_LENGTH)
+  
+  while True:
+    message, digest, success = proof_of_work(s=s,
+                                             msg_len=MESSAGE_LENGTH, 
+                                             difficulty=difficulty,
+                                             timeout=GIT_POLL_INTERVAL)
+    repo, fastforward = fastforward_repo(repo=repo, 
+                                         repo_dir=REPO_DIR, 
+                                         remote_branch=REMOTE_BRANCH)
+    if success and not fastforward:
+      print("mined gitcoin! commit-hash:[%s]" % (message, digest),
+            file=sys.stderr)
+    elif fastforward:
+        # re-add our gitcoin to the reset --hard'd repository 
+        difficulty = open(DIFFICULTY_PATH,"r").read().split()[0]
+        update_ledger(ledger_path=LEDGER_PATH, username=USERNAME)
+        repo, tree = index_add_to_repo(repo=repo, 
+                                       repo_dir=REPO_DIR, 
+                                       filename=LEDGER_FILENAME)
+        s = gen_commit_context(repo=repo, 
+                               tree=tree.hex,
+                               username=USERNAME, 
+                               email=EMAIL, 
+                               time=commit_time, 
+                               msg_len=MESSAGE_LENGTH)
+
+  c = gen_commit(repo=repo,
+                 tree=tree, 
+                 username=USERNAME, 
+                 email=EMAIL, 
+                 time=commit_time, 
+                 message=message)
+
+  # make sure we really have a winner 
+  s.update(message)
+  if c.hex != s.hexdigest():
+    raise Exception("commit hash mismatch!")
+
+  # again, making up for pygit2's shortcomings...
+  subprocess.call(["git", "push", "origin", "master"], 
+                  cwd=REPO_DIR, 
+                  stdout=open(os.devnull,'w'), 
+                  stderr=subprocess.STDOUT)
+  # maybe dont need separate dirs?
+  shutil.rmtree(REPO_DIR, ignore_errors=True)
my-miner
@@ -1,97 +0,0 @@
-#!/bin/bash
-
-set -eu
-
-if [ "$#" != 2 ]; then
-    echo >&2 "Usage: $0 <clone_url> <public_username>
-
-A VERY SLOW mining implementation. This should give you an idea of
-where to start, but it probably won't successfully mine you any
-Gitcoins.
-
-Arguments:
-
-<clone_url> is the string you'd pass to git clone (i.e.
-  something of the form username@hostname:path)
-
-<public_username> is the public username provided to you in
-  the CTF web interface."
-    exit 1
-fi
-
-export clone_spec=$1
-export public_username=$2
-
-prepare_index() {
-    perl -i -pe 's/($ENV{public_username}: )(\d+)/$1 . ($2+1)/e' LEDGER.txt
-    grep -q "$public_username" LEDGER.txt || echo "$public_username: 1" >> LEDGER.txt
-
-    git add LEDGER.txt
-}
-
-solve() {
-  # Brute force until you find something that's lexicographically
-  # small than $difficulty.
-  difficulty=$(cat difficulty.txt)
-
-  # Create a Git tree object reflecting our current working
-  # directory
-  tree=$(git write-tree)
-  parent=$(git rev-parse HEAD)
-  timestamp=$(date +%s)
-	body="tree $tree
-parent $parent
-author CTF user <me@example.com> $timestamp +0000
-committer CTF user <me@example.com> $timestamp +0000
-
-Give me a Gitcoin
-
-"
-
-	# See http://git-scm.com/book/en/Git-Internals-Git-Objects for
-	# details on Git objects.
-  #time sha1=$(echo "${body}${counter}" | git hash-object -t commit --stdin)
-  #sha1=$(git hash-object -t commit --stdin <<< "${body}${counter}")
-  coin=$(python ../miner/gitcoin.py <<< "$body")
-	#git hash-object -t commit --stdin -w <<< "$coin" > /dev/null
-  sha1=$(git hash-object -t commit --stdin <<< "$coin")
-  echo "SHA1 $sha1"
-  (printf "commit %s\0" $(wc -c <<< "$body"); cat <<<"$body") | shasum
-  (printf "commit %s\0" $(wc -c <<< "$coin"); cat <<<"$coin") | shasum
-	#git reset --hard "$sha1"
-  break
-}
-
-reset() {
-    git fetch origin master >/dev/null 2>/dev/null
-    git reset --hard origin/master >/dev/null
-    git stash >/dev/null
-    git pull
-    echo "Current difficulty:"
-    cat difficulty.txt
-}
-
-# Set up repo
-local_path=./${clone_spec##*:}
-
-if [ -d "$local_path" ]; then
-    echo "Using existing repository at $local_path"
-    cd "$local_path"
-else
-    echo "Cloning repository to $local_path"
-    git clone "$clone_spec" "$local_path"
-    cd "$local_path"
-fi
-
-while true; do
-    reset
-    prepare_index
-    solve
-    if git push origin master; then
-	echo "Success :)"
-	break
-    else
-	echo "Starting over :("
-	reset
-    fi
-done
old.py
@@ -1,31 +0,0 @@
-#!/usr/bin/python
-from __future__ import print_function
-import sys
-import time
-#from random import random
-from hashlib import sha1
-
-def githash(data):
-  s = sha1()
-  s.update("commit %u\0" % len(data))
-  s.update(data)
-  return s.hexdigest()
-
-if __name__ == "__main__":
-  counter = 0
-  body = sys.stdin.read()
-  difficulty = open("./difficulty.txt","r").read().split()[0]
-  while True:
-    start = time.time()
-    #print(counter%1000, file=sys.stderr)
-    #nonce = sha1(str(random())).hexdigest()
-    counter += 1
-    commit = body+str(counter)
-    #if githash(body+str(counter)) < difficulty:
-    print(commit)
-    print("bodyhash "+githash(body), file=sys.stderr)
-    print("githash "+githash(commit), file=sys.stderr)
-    break
-    end = time.time()
-    print("%g Kh/s ... %d " % (1/(end-start)/1024, counter), end='', file=sys.stderr)
-