master
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)