From 6654544ca7245fa0f501057a94e74c8e331af2b0 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Thu, 19 Dec 2013 13:38:09 -0500 Subject: [PATCH] Added initial files --- blocks.py | 185 +++++++++++++++++++++++++++++++++++++++++ parser.py | 5 ++ processblock.py | 214 ++++++++++++++++++++++++++++++++++++++++++++++++ rlp.py | 50 +++++++++++ transactions.py | 30 +++++++ 5 files changed, 484 insertions(+) create mode 100644 blocks.py create mode 100644 parser.py create mode 100644 processblock.py create mode 100644 rlp.py create mode 100644 transactions.py diff --git a/blocks.py b/blocks.py new file mode 100644 index 0000000000..ee519ca8cf --- /dev/null +++ b/blocks.py @@ -0,0 +1,185 @@ +from pybitcointools import * +import rlp +import re +from transactions import Transaction + +class Block(): + def __init__(self,data=None): + if not data: + return + if re.match('^[0-9a-fA-F]*$',data): + data = data.decode('hex') + header, tree_node_list, transaction_list, sibling_list = rlp.decode(data) + h = rlp.decode(header) + self.prevhash = encode(h[0],16,64) + self.coinbase = encode(h[1],16,40) + self.balance_root = encode(h[2],256,32) + self.contract_root = encode(h[3],256,32) + self.difficulty = h[4] + self.timestamp = h[5] + transactions_root = encode(h[6],256,32) + siblings_root = encode(h[7],256,32) + self.nonce = h[8] + self.datastore = {} + for nd in rlp.decode(tree_node_list): + ndk = bin_sha256(nd) + self.datastore[ndk] = rlp.decode(nd) + self.transactions = [Transaction(x) for x in rlp.decode(transaction_list)] + self.siblings = [rlp.decode(x) for x in rlp.decode(sibling_list)] + # Verifications + if self.balance_root != '' and self.balance_root not in self.datastore: + raise Exception("Balance Merkle root not found!") + if self.contract_root != '' and self.contract_root not in self.datastore: + raise Exception("Contract Merkle root not found!") + if bin_sha256(transaction_list) != transactions_root: + raise Exception("Transaction list root hash does not match!") + if bin_sha256(sibling_list) != sibling_root: + raise Exception("Transaction list root hash does not match!") + for sibling in self.siblings: + if sibling[0] != self.prevhash: + raise Exception("Sibling's parent is not my parent!") + + + hexalpha = '0123456789abcdef' + + def get_updated_state(self,node,key,value): + curnode = self.datastore.get(node,None) + # Insertion case + if value != 0 and value != '': + # Base case + if key == '': + return value + # Inserting into an empty trie + if not curnode: + newnode = [ key, value ] + k = sha256(rlp.encode(newnode)) + self.datastore[k] = newnode + return k + elif len(curnode) == 2: + # Inserting into a (k,v), same key + if key == curnode[0]: + newnode = [ key, value ] + k = sha256(rlp.encode(newnode)) + self.datastore[k] = newnode + return k + # Inserting into a (k,v), different key + else: + i = 0 + while key[:i] == curnode[0][:i]: i += 1 + k1 = self.get_updated_state(None,curnode[0][i:],curnode[1]) + k2 = self.get_updated_state(None,key[i:],value) + newnode3 = [ None ] * 16 + newnode3[ord(curnode[0][0])] = k1 + newnode3[ord(key[0])] = k2 + k3 = sha256(rlp.encode(newnode3)) + self.datastore[k3] = newnode3 + # No prefix sharing + if i == 1: + return k3 + # Prefix sharing + else: + newnode4 = [ key[:i-1], k3 ] + k4 = sha256(rlp.encode(newnode4)) + self.datastore[k4] = newnode4 + return k4 + else: + # inserting into a 16-array + newnode1 = self.get_updated_state(curnode[ord(key[0])],key[1:],value) + newnode2 = [ curnode[i] for i in range(16) ] + newnode2[ord(key[0])] = newnode1 + return newnode2 + # Deletion case + else: + # Base case + if key == '': + return None + # Deleting from a (k,v); obvious + if len(curnode) == 2: + if key == curnode[0]: return None + else: return node + else: + k1 = self.get_updated_state(curnode[ord(key[0])],key[1:],value) + newnode = [ curnode[i] for i in range(16) ] + newnode[ord(key[0])] = k1 + totalnodes = sum([ 1 if newnode2[i] else 0 for i in range(16) ]) + if totalnodes == 0: + raise Exception("Can't delete from two to zero! Error! Waahmbulance!") + elif totalnodes == 1: + # If only have one node left, we revert to (key, value) + node_index = [i for i in range(16) if newnode2[i]][0] + node2 = self.datastore[curnode[node_index]] + if len(node2) == 2: + # If it's a (key, value), we just prepend to the key + newnode = [ chr(node_index) + node2[0], node2[1] ] + else: + # Otherwise, we just make a single-char (key, value) pair + newnode = [ chr(node_index), curnode[node_index] ] + k2 = sha256(rlp.encode(newnode)) + self.datastore[k2] = newnode + return k2 + + + def update_balance(self,address,value): + # Use out internal representation for the key + key = ''.join([chr(hexalpha.find(x)) for x in address.encode('hex')]) + self.balance_root = self.get_updated_state(self.balance_root,key,value) + + def update_contract_state(self,address,index,value): + # Use out internal representation for the key + key = ''.join([chr(hexalpha.find(x)) for x in (address+index).encode('hex')]) + self.contract_root = self.get_updated_state(self.contract_root,key,value) + + def get_state_value(self,node,key): + if key == '': + return node + if not curnode: + return None + curnode = self.datastore.get(node,None) + return self.get_state_value(curnode[ord(key[0])],key[1:]) + + def get_balance(self,address): + # Use out internal representation for the key + key = ''.join([chr(hexalpha.find(x)) for x in (address).encode('hex')]) + return self.get_state_value(self.balance_root,key) + + def get_contract_state(self,address,index): + # Use out internal representation for the key + key = ''.join([chr(hexalpha.find(x)) for x in (address+index).encode('hex')]) + return self.get_state_value(self.contract_root,key) + + def get_state_size(self,node): + if node is None: return 0 + curnode = self.datastore.get(node,None) + if not curnode: return 0 + elif len(curnode) == 2: + return self.get_state_size(curnode[1]) + else: + total = 0 + for i in range(16): total += self.get_state_size(curnode[i]) + return total + + def get_contract_size(self,address): + # Use out internal representation for the key + key = ''.join([chr(hexalpha.find(x)) for x in (address).encode('hex')]) + return self.get_state_size(self.get_state_value(self.contract_root,key)) + + def serialize(self): + nodes = {} + def process(node): + if node is None: return + curnode = self.datastore.get(node,None) + if curnode: + index = sha256(rlp.encode(curnode)) + nodes[index] = curnode + if len(node) == 2: + process(curnode[1]) + elif len(node) == 16: + for i in range(16): process(curnode[i]) + process(self.balance_root) + process(self.contract_root) + tree_nodes = [nodes[x] for x in nodes] + nodelist = rlp.encode(tree_nodes) + txlist = rlp.encode([x.serialize() for x in self.transactions]) + siblinglist = rlp.encode(self.siblings) + header = rlp.encode([self.prevhash, self.coinbase, self.balance_root, self.contract_root, self.difficulty, self.timestamp, bin_sha256(txlist), bin_sha256(siblinglist]) + return rlp.encode([header, nodelist, txlist, siblinglist]) diff --git a/parser.py b/parser.py new file mode 100644 index 0000000000..00823aeaa3 --- /dev/null +++ b/parser.py @@ -0,0 +1,5 @@ +import rlp + +def parse(inp): + if inp[0] == '\x00': + return { "type": "transaction", "data": rlp.parse( diff --git a/processblock.py b/processblock.py new file mode 100644 index 0000000000..20d7edb4da --- /dev/null +++ b/processblock.py @@ -0,0 +1,214 @@ +from transactions import Transaction +from blocks import Block + +scriptcode_map = { + 0x00: 'STOP', + 0x10: 'ADD', + 0x11: 'SUB', + 0x12: 'MUL', + 0x13: 'DIV', + 0x14: 'SDIV', + 0x15: 'MOD', + 0x16: 'SMOD', + 0x17: 'EXP', + 0x20: 'LT', + 0x21: 'LE', + 0x22: 'GT', + 0x23: 'GE', + 0x24: 'EQ', + 0x25: 'NEG', + 0x26: 'NOT', + 0x30: 'SHA256', + 0x31: 'RIPEMD-160', + 0x32: 'ECMUL', + 0x33: 'ECADD', + 0x34: 'SIGN', + 0x35: 'RECOVER', + 0x40: 'COPY', + 0x41: 'STORE', + 0x42: 'LD', + 0x43: 'SET', + 0x50: 'JMP', + 0x51: 'JMPI', + 0x52: 'IND', + 0x60: 'EXTRO', + 0x61: 'BALANCE', + 0x70: 'MKTX', + 0x71: 'RAWTX', + 0x80: 'DATA', + 0x81: 'DATAN', + 0x90: 'MYADDRESS' +} + +fees = { + 'stepfee': 2**60 * 8192, + 'txfee': 2**60 * 524288, + 'memoryfee': 2**60 * 262144, + 'memory_adjust_fee': 2**60 * 65536 +} + +def eval_contract(block,tx): + address = tx.to + # Initialize registers + reg = [0] * 256 + reg[0] = decode(tx.from,16) + reg[1] = decode(tx.to,16) + reg[2] = tx.value + reg[3] = tx.fee + index = 0 + stepcounter = 0 + def monop(code,f): + reg[code[2]] = f(reg[code[1]]) + def binop(code,f): + reg[code[3]] = f(reg[code[1]],reg[code[2]]) + while 1: + # Calculate fee + totalfee = 0 + stepcounter += 1 + if stepcounter > 16: + totalfee += fees.get("stepfee") + val_at_index = decode(block.get_contract_state(address,encode(index,256,32)),256) + code = [ int(val_at_index / 256**i) % 256 for i in range(6) ] + c = scriptcode_map[code[0]] + if c == 'STORE': + existing = block.get_contract_state(address,code[2]) + if reg[code[1]] != 0: fee += fees["MEMORYFEE"] + if existing: fee -= fees["MEMORYFEE"] + contractbalance = block.get_balance(address) + # If we can't pay the fee... + if fee > contractbalance: + return state + # Otherwise, pay it + block.set_balance(address,contractbalance - fee) + + if c == 'STOP': + break + elif c == 'ADD': + reg[code[3]] = (reg[code[1]] + reg[code[2]]) % 2**256 + elif c == 'MUL': + reg[code[3]] = (reg[code[1]] * reg[code[2]]) % 2**256 + elif c == 'SUB': + reg[code[3]] = (reg[code[1]] + 2**256 - reg[code[2]]) % 2**256 + elif c == 'DIV': + reg[code[3]] = int(reg[code[1]] / reg[code[2]]) + elif c == 'SDIV': + sign = 1 + sign *= (1 if reg[code[1]] < 2**255 else -1) + sign *= (1 if reg[code[2]] < 2**255 else -1) + x = reg[code[1]] if reg[code[1]] < 2**255 else 2**256 - reg[code[1]] + y = reg[code[2]] if reg[code[2]] < 2**255 else 2**256 - reg[code[2]] + z = int(x/y) + reg[code[3]] = z if sign = 1 else 2**256 - z + elif code == 'MOD': + reg[code[3]] = reg[code[1]] % reg[code[2]] + elif code == 'SMOD': + sign = 1 + sign *= (1 if reg[code[1]] < 2**255 else -1) + sign *= (1 if reg[code[2]] < 2**255 else -1) + x = reg[code[1]] if reg[code[1]] < 2**255 else 2**256 - reg[code[1]] + y = reg[code[2]] if reg[code[2]] < 2**255 else 2**256 - reg[code[2]] + z = x%y + reg[code[3]] = z if sign = 1 else 2**256 - z + elif code == 'EXP': + reg[code[3]] = pow(reg[code[1]],reg[code[2]],2**256) + elif code == 'NEG': + reg[code[2]] = 2**256 - reg[code[1]] + elif code == 'LT': + reg[code[3]] = 1 if reg[code[1]] < reg[code[2]] else 0 + elif code == 'LE': + reg[code[3]] = 1 if reg[code[1]] <= reg[code[2]] else 0 + elif code == 'GT': + reg[code[3]] = 1 if reg[code[1]] > reg[code[2]] else 0 + elif code == 'GE': + reg[code[3]] = 1 if reg[code[1]] >= reg[code[2]] else 0 + elif code == 'EQ': + reg[code[3]] = 1 if reg[code[1]] == reg[code[2]] else 0 + elif code == 'NOT': + reg[code[2]] = 1 if reg[code[1]] == 0 else 0 + elif code == 'SHA256': + inp = encode(reg[code[1]],256,32) + reg[code[2]] = decode(hashlib.sha256(inp).digest(),256) + elif code == 'RIPEMD-160': + inp = encode(reg[code[1]],256,32) + reg[code[2]] = decode(hashlib.new('ripemd160',inp).digest(),256) + elif code == 'ECMUL': + pt = (reg[code[1]],reg[code[2]]) + # Point at infinity + if pt[0] == 0 and pt[1] == 0: + reg[code[4]], reg[code[5]] = 0,0 + # Point not on curve, coerce to infinity + elif (pt[0] ** 3 + 7 - pt[1] ** 2) % N != 0: + reg[code[4]], reg[code[5]] = 0,0 + # Legitimate point + else: + pt2 = base10_multiply(pt,reg[code[3]]) + reg[code[4]], reg[code[5]] = pt2[0], pt2[1] + elif code == 'ECADD': + pt1 = (reg[code[1]],reg[code[2]]) + pt2 = (reg[code[3]],reg[code[4]]) + if (pt1[0] ** 3 + 7 - pt1[1] ** 2) % N != 0: + reg[code[5]], reg[code[6]] = 0,0 + elif (pt2[0] ** 3 + 7 - pt2[1] ** 2) % N != 0: + reg[code[5]], reg[code[6]] = 0,0 + else: + pt3 = base10_add(pt1,pt2) + reg[code[5]], reg[code[6]] = pt3[0], pt3[1] + elif code == 'SIGN': + reg[code[3]], reg[code[4]], reg[code[5]] = ecdsa_raw_sign(reg[code[1]],reg[code[2]]) + elif code == 'RECOVER': + pt = ecdsa_raw_recover((reg[code[2]],reg[code[3]],reg[code[4]]),reg[code[1]]) + reg[code[5]] = pt[0] + reg[code[6]] = pt[1] + elif code == 'COPY': + reg[code[2]] = reg[code[1]] + elif code == 'STORE': + block.update_contract_state(address,encode(reg[code[2]],256,32),reg[code[1]]) + elif code == 'LD': + reg[code[2]] = block.get_contract_state(address,encode(reg[code[1]],256,32)) + elif code == 'SET': + reg[code[1]] = (code[2] + 256 * code[3] + 65536 * code[4] + 16777216 * code[5]) * 2**code[6] % 2**256 + elif code == 'JMP': + index = code[1] + elif code == 'JMPI': + if reg[code[1]]: index = code[2] + elif code == 'JMPX': + index = reg[code[1]] + elif code == 'JMPIX': + if reg[code[1]]: index = reg[code[2]] + elif code == 'IND': + reg[code[1]] = index + elif code == 'EXTRO': + reg[code[3]] = ethdb.get(ethdb.get(state.contract,enc160(reg[code[1]])),enc256(reg[code[2]])) + elif code == 'BALANCE': + reg[code[2]] = ethdb.get(state.balance,enc160(reg[code[1]])) + elif code == 'MKTX': + ntx = { + "ins": [ tx["receiver"] ], + "oindex": 0, + "sender": tx["receiver"], + "receiver": reg[code[1]], + "value": reg[code[2]], + "fee": reg[code[3]], + "data": [] + } + for i in range(reg[code[4]]): + ntx["data"].append(ethdb.get(contract,(reg[code[5]]+i) % 2**256)) + fee += ntx["fee"] + if fee > contractbalance: + return state + state.txs.append(tx) + elif code == 'DATA': + reg[code[2]] = tx["data"][code[1]] + elif code == 'DATAX': + reg[code[2]] = tx["data"][ethdb.get(contract,enc256(code[1]))] + elif code == 'DATAN': + reg[code[2]] = len(tx["data"]) + elif code == 'MYADDRESS': + reg[code[1]] = tx["receiver"] + elif code == 'SUICIDE': + sz = ethdb.size(contract) + fee -= sz * state.fees["MEMORYFEE"] + contract = None + state.balance = ethdb.set(state.balance,tx["receiver"],contractbalance - fee) + state.contract = contract + return state diff --git a/rlp.py b/rlp.py new file mode 100644 index 0000000000..3b2f16c15a --- /dev/null +++ b/rlp.py @@ -0,0 +1,50 @@ +def binary_length(n): + if n == 0: return 0 + else: return 1 + binary_length(n / 256) + +def to_binary_array(n,L=None): + if L is None: L = binary_length(n) + if n == 0: return [] + else: + x = to_binary_array(n / 256) + x.append(n % 256) + return x + +def to_binary(n,L=None): return ''.join([chr(x) for x in to_binary_array(n,L)]) + +def from_binary(b): + if len(b) == 0: return 0 + else: return ord(from_binary(b[:-1])) * 256 + b[-1] + +def num_to_var_int(n): + if n < 253: s = chr(n) + else if n < 2**16: s = [253] + list(reversed(to_binary_array(n,2))) + else if n < 2**32: s = [254] + list(reversed(to_binary_array(n,4))) + else if n < 2**64: s = [255] + list(reversed(to_binary_array(n,8))) + else raise Exception("number too big") + return ''.join([chr(x) for x in s]) + +def decode(s): + o = [] + index = 0 + def read_var_int(): + si = ord(s[index]) + index += 1 + if si < 253: return s[index - 1] + elif si == 253: read = 2 + elif si == 254: read = 4 + elif si == 255: read = 8 + index += read + return from_binary(s[index-read:index]) + while index < len(s): + L = read_var_int() + o.append(s[index:index+L]) + return o + +def encode(s): + if isinstance(s,(int,long)): return encode(to_binary(s)) + if isinstance(s,str): return num_to_var_int(len(s))+s + else: + x = ''.join([encode(x) for x in s]) + return num_to_var_int(len(s))+s + diff --git a/transactions.py b/transactions.py new file mode 100644 index 0000000000..d9b26b8f82 --- /dev/null +++ b/transactions.py @@ -0,0 +1,30 @@ +from pybitcointools import * +import rlp +import re + +class Transaction(): + def __init__(*args): + + def lpad(inp,L): return '\x00' * max(0,L - len(inp)) + inp + + def parse(self,data): + if re.match('^[0-9a-fA-F]*$',data): + data = data.decode('hex') + o = rlp.unparse(data) + self.to = lpad(o[0],20) + self.value = decode(o[1],256) + self.fee = decode(o[2],256) + self.data = rlp.unparse(o[-3]) + self.sig = o[-4] + rawhash = sha256(rlp.encode([self.to,self.value,self.fee,self.data])) + v,r,s = ord(self.sig[0]), decode(self.sig[1:33],256), decode(self.sig[33:],256) + self.from = hash160(ecdsa_raw_recover(rawhash,(v,r,s))) + def sign(self,key): + rawhash = sha256(rlp.parse([self.to,self.value,self.fee,self.data])) + v,r,s = ecdsa_raw_sign(rawhash,args[5]) + self.sig = chr(v)+encode(r,256,32)+encode(s,256,32) + self.from = hash160(privtopub(args[5])) + def serialize(self): + return rlp.parse([self.to, self.value, self.fee, self.data, self.sig]).encode('hex') + def hash(self): + return bin_sha256(self.serialize())