From c489ff0cbbe2dfd0049fd3e793c1c49ccb388545 Mon Sep 17 00:00:00 2001 From: Vitalik Buterin Date: Thu, 26 Dec 2013 16:17:18 -0500 Subject: [PATCH] Got the block updating, serializing and deserializing to work at least without contracts --- blocks.py | 95 ++++++++++++++---------------------------- manager.py | 89 +++++++++++++++++++++++++++++++++++++++ processblock.py | 108 ++++++++++++++++++++++++++++++++++-------------- rlp.py | 12 +++--- transactions.py | 20 +++++---- trie.py | 39 +++++++++++++++++ 6 files changed, 254 insertions(+), 109 deletions(-) create mode 100644 manager.py diff --git a/blocks.py b/blocks.py index 097b71cb0f..c9e94c2098 100644 --- a/blocks.py +++ b/blocks.py @@ -3,6 +3,7 @@ import rlp import re from transactions import Transaction from trie import Trie +import sys class Block(): def __init__(self,data=None): @@ -14,102 +15,68 @@ class Block(): data = data.decode('hex') header, transaction_list, self.uncles = rlp.decode(data) - [ number, + [ self.number, self.prevhash, self.uncles_root, self.coinbase, state_root, self.transactions_root, - diff, - timestamp, - nonce, + self.difficulty, + self.timestamp, + self.nonce, self.extra ] = header - self.number = decode(number,256) - self.difficulty = decode(difficulty,256) - self.timestamp = decode(timestamp,256) - self.nonce = decode(nonce,256) - self.transactions = [Transaction(x) for x in transaction_list)] + self.transactions = [Transaction(x) for x in transaction_list] self.state = Trie('statedb',state_root) + self.reward = 0 # Verifications - if self.state.root != '' and self.state.__get_state(self.state.root,[]) == '': + if self.state.root != '' and self.state.db.get(self.state.root) == '': raise Exception("State Merkle root not found in database!") - if bin_sha256(transaction_list) != transactions_root: + if bin_sha256(rlp.encode(transaction_list)) != self.transactions_root: raise Exception("Transaction list root hash does not match!") - if bin_sha256(uncle_list) != uncle_root: + if bin_sha256(rlp.encode(self.uncles)) != self.uncles_root: raise Exception("Uncle root hash does not match!") # TODO: check POW - def send(self,tx): - # Subtract value and fee from sender account and increment nonce if applicable - sender_state = rlp.decode(self.state.get(tx.from)) - if not sender_state: - return False - sender_value = decode(sender_state[1],256) - if value + fee > sender_value: - return False - sender_state[1] = encode(sender_value - value - fee,256) - # Nonce applies only to key-based addresses - if decode(sender_state[0],256) == 0: - if decode(sender_state[2],256) != tx.nonce: - return False - sender_state[2] = encode(tx.nonce + 1,256) - self.state.update(tx.from,sender_state) - # Add money to receiver - if tx.to > '': - receiver_state = rlp.decode(self.state.get(tx.to)) or ['', '', ''] - receiver_state[1] = encode(decode(receiver_state[1],256) + value,256) - self.state.update(tx.to,receiver_state) - # Create a new contract - else: - addr = tx.hash()[-20:] - contract = block.get_contract(addr) - if contract.root != '': return False - for i in range(len(tx.data)): - contract.update(encode(i,256,32),tx.data[i]) - block.update_contract(addr) - # Pay fee to miner - miner_state = rlp_decode(self.state.get(self.coinbase)) or ['','',''] - miner_state[1] = encode(decode(miner_state[1],256) + fee,256) - self.state.update(self.coinbase,miner_state) - return True - def pay_fee(self,address,fee,tominer=True): # Subtract fee from sender sender_state = rlp.decode(self.state.get(address)) - if not sender_state: + if not sender_state or sender_state[1] < fee: return False - sender_value = decode(sender_state[1],256) - if sender_value < fee: - return False - sender_state[1] = encode(sender_value - fee,256) + sender_state[1] -= fee self.state.update(address,sender_state) # Pay fee to miner if tominer: - miner_state = rlp.decode(self.state.get(self.coinbase)) or ['','',''] - miner_state[1] = encode(decode(miner_state[1],256) + fee,256) + miner_state = rlp.decode(self.state.get(self.coinbase)) or [0,0,0] + miner_state[1] += fee self.state.update(self.coinbase,miner_state) return True def get_nonce(self,address): state = rlp.decode(self.state.get(address)) - if not state or decode(state[0],256) == 0: return False - return decode(state[2],256) + if not state or state[0] == 0: return False + return state[2] def get_balance(self,address): state = rlp.decode(self.state.get(address)) - return decode(state[1] || '',256) + return state[1] if state else 0 + + def set_balance(self,address,balance): + state = rlp.decode(self.state.get(address)) or [0,0,0] + state[1] = balance + self.state.update(address,rlp.encode(state)) + # Making updates to the object obtained from this method will do nothing. You need # to call update_contract to finalize the changes. def get_contract(self,address): state = rlp.decode(self.state.get(address)) - if not state or decode(state[0],256) == 0: return False + if not state or state[0] == 0: return False return Trie('statedb',state[2]) def update_contract(self,address,contract): - state = rlp.decode(self.state.get(address)) - if not state or decode(state[0],256) == 0: return False + state = rlp.decode(self.state.get(address)) or [1,0,''] + if state[0] == 0: return False state[2] = contract.root self.state.update(address,state) @@ -117,15 +84,15 @@ class Block(): # assuming no verification failures def serialize(self): txlist = [x.serialize() for x in self.transactions] - header = [ encode(self.number,256), + header = [ self.number, self.prevhash, bin_sha256(rlp.encode(self.uncles)), self.coinbase, self.state.root, - bin_sha256(rlp.encode(self.txlist)), - encode(self.difficulty,256), - encode(self.timestamp,256), - encode(self.nonce,256), + bin_sha256(rlp.encode(txlist)), + self.difficulty, + self.timestamp, + self.nonce, self.extra ] return rlp.encode([header, txlist, self.uncles ]) diff --git a/manager.py b/manager.py new file mode 100644 index 0000000000..dbe0f37cdd --- /dev/null +++ b/manager.py @@ -0,0 +1,89 @@ +import rlp +import leveldb +from blocks import Block +from transactions import Transaction +import processblock +import hashlib +from pybitcointools import * + +txpool = {} + +genesis_header = [ + 0, + '', + bin_sha256(rlp.encode([])), + '', + '', + bin_sha256(rlp.encode([])), + 2**36, + 0, + 0, + '' +] + +genesis = [ genesis_header, [], [] ] + +mainblk = Block(rlp.encode(genesis)) + +db = leveldb.LevelDB("objects") + +def genaddr(seed): + priv = bin_sha256(seed) + addr = bin_sha256(privtopub(priv)[1:])[-20:] + return priv,addr + +# For testing +k1,a1 = genaddr("123") +k2,a2 = genaddr("456") + +def broadcast(obj): + pass + +def receive(obj): + d = rlp.decode(obj) + # Is transaction + if len(d) == 8: + tx = Transaction(obj) + if mainblk.get_balance(tx.sender) < tx.value + tx.fee: return + if mainblk.get_nonce(tx.sender) != tx.nonce: return + txpool[bin_sha256(blk)] = blk + broadcast(blk) + # Is message + elif len(d) == 2: + if d[0] == 'getobj': + try: return db.Get(d[1][0]) + except: + try: return mainblk.state.db.get(d[1][0]) + except: return None + elif d[0] == 'getbalance': + try: return mainblk.state.get_balance(d[1][0]) + except: return None + elif d[0] == 'getcontractroot': + try: return mainblk.state.get_contract(d[1][0]).root + except: return None + elif d[0] == 'getcontractsize': + try: return mainblk.state.get_contract(d[1][0]).get_size() + except: return None + elif d[0] == 'getcontractstate': + try: return mainblk.state.get_contract(d[1][0]).get(d[1][1]) + except: return None + # Is block + elif len(d) == 3: + blk = Block(obj) + p = block.prevhash + try: + parent = Block(db.Get(p)) + except: + return + uncles = block.uncles + for s in uncles: + try: + sib = db.Get(s) + except: + return + processblock.eval(parent,blk.transactions,blk.timestamp,blk.coinbase) + if parent.state.root != blk.state.root: return + if parent.difficulty != blk.difficulty: return + if parent.number != blk.number: return + db.Put(blk.hash(),blk.serialize()) + diff --git a/processblock.py b/processblock.py index 3bbfa61636..64a233ac35 100644 --- a/processblock.py +++ b/processblock.py @@ -1,6 +1,8 @@ from transactions import Transaction from blocks import Block import time +import sys +import rlp scriptcode_map = { 0x00: 'STOP', @@ -61,27 +63,64 @@ params = { 'period_4_reward': 2**80 * 128 } +def process_transactions(block,transactions): + while len(transactions) > 0: + tx = transactions.pop(0) + enc = (tx.value, tx.fee, tx.sender.encode('hex'), tx.to.encode('hex')) + sys.stderr.write("Attempting to send %d plus fee %d from %s to %s\n" % enc) + # Grab data about sender, recipient and miner + sdata = rlp.decode(block.state.get(tx.sender)) or [0,0,0] + tdata = rlp.decode(block.state.get(tx.to)) or [0,0,0] + # Calculate fee + if tx.to == '\x00'*20: + fee = params['newcontractfee'] + len(tx.data) * params['memoryfee'] + else: + fee = params['txfee'] + # Insufficient fee, do nothing + if fee > tx.fee: + sys.stderr.write("Insufficient fee\n") + continue + # Too much data, do nothing + if len(tx.data) > 256: + sys.stderr.write("Too many data items\n") + continue + if not sdata or sdata[1] < tx.value + tx.fee: + sys.stderr.write("Insufficient funds to send fee\n") + continue + elif tx.nonce != sdata[2] and sdata[0] == 0: + sys.stderr.write("Bad nonce\n") + continue + # Try to send the tx + if sdata[0] == 0: sdata[2] += 1 + sdata[1] -= (tx.value + tx.fee) + block.reward += tx.fee + if tx.to != '': + tdata[1] += tx.value + else: + addr = tx.hash()[-20:] + adata = rlp.decode(block.state.get(addr)) + if adata[2] != '': + sys.stderr.write("Contract already exists\n") + continue + block.state.update(addr,rlp.encode([1,tx.value,''])) + contract = block.get_contract(addr) + for i in range(len(tx.data)): + contract.update(encode(i,256,32),tx.data[i]) + block.update_contract(addr) + print sdata, tdata + block.state.update(tx.sender,rlp.encode(sdata)) + block.state.update(tx.to,rlp.encode(tdata)) + # Evaluate contract if applicable + if tdata[0] == 1: + eval_contract(block,transactions,tx) + sys.stderr.write("tx processed\n") + def eval(block,transactions,timestamp,coinbase): h = block.hash() # Process all transactions - while len(transactions) > 0: - tx = transactions.pop(0) - fee = 0 - if tx.to = '\x00'*20: - fee += params['newcontractfee'] + len(tx.data) * params['memoryfee'] - else: - fee += params['txfee'] - # Insufficient fee, do nothing - if fee > tx.fee: continue - # Too much data, do nothing - if len(data) > 256: continue - # Try to send the tx - if not block.send(tx): continue - # Evaluate contract if applicable - eval_contract(block,transactions,tx) + process_transactions(block,transactions) # Pay miner fee - miner_state = rlp_decode(self.state.get(self.coinbase)) or ['','',''] - miner_balance = decode(miner_state[1],256) + miner_state = rlp.decode(block.state.get(block.coinbase)) or [0,0,0] block.number += 1 reward = 0 if block.number < params['period_1_duration']: @@ -92,17 +131,17 @@ def eval(block,transactions,timestamp,coinbase): reward = params['period_3_reward'] else: reward = params['period_4_reward'] - miner_balance += reward + print reward + miner_state[1] += reward + block.reward for uncle in block.uncles: - sib_miner_state = rlp_decode(self.state.get(uncle[3])) + sib_miner_state = rlp_decode(block.state.get(uncle[3])) sib_miner_state[1] = encode(decode(sib_miner_state[1],256)+reward*7/8,256) - self.state.update(uncle[3],sib_miner_state) - miner_balance += reward/8 - miner_state[1] = encode(miner_balance,256) - self.state.update(self.coinbase,miner_state) + block.state.update(uncle[3],sib_miner_state) + miner_state[1] += reward/8 + block.state.update(block.coinbase,rlp.encode(miner_state)) # Check timestamp if timestamp < block.timestamp or timestamp > int(time.time()) + 3600: - raise new Exception("timestamp not in valid range!") + raise Exception("timestamp not in valid range!") # Update difficulty if timestamp >= block.timestamp + 42: block.difficulty += int(block.difficulty / 1024) @@ -115,10 +154,11 @@ def eval(block,transactions,timestamp,coinbase): return block def eval_contract(block,transaction_list,tx): + sys.stderr.write("evaluating contract\n") address = tx.to # Initialize registers reg = [0] * 256 - reg[0] = decode(tx.from,256) + reg[0] = decode(tx.sender,256) reg[1] = decode(tx.to,256) reg[2] = tx.value reg[3] = tx.fee @@ -131,8 +171,11 @@ def eval_contract(block,transaction_list,tx): # Convert the data item into a code piece val_at_index = decode(contract.get(encode(index,256,32)),256) code = [ int(val_at_index / (256**i)) % 256 for i in range(6) ] + code[0] = scriptcode_map.get(code[0],'INVALID') + sys.stderr.write("Evaluating: "+ str(code)+"\n") # Invalid code instruction or STOP code stops execution sans fee - if val_at_index >= 256**6 or code[0] == 'STOP': + if val_at_index >= 256**6 or code[0] in ['STOP','INVALID']: + sys.stderr.write("stop code, exiting\n") break # Calculate fee minerfee = 0 @@ -154,10 +197,11 @@ def eval_contract(block,transaction_list,tx): # If we can't pay the fee, break, otherwise pay it if block.get_balance(address) < minerfee + nullfee: + sys.stderr.write("insufficient fee, exiting\n") break - block.pay_fee(address,nullfee,False) - block.pay_fee(address,minerfee,True) - + block.set_balance(address,block.get_balance(address) - nullfee - minerfee) + block.reward += minerfee + sys.stderr.write("evaluating operation\n") # Evaluate operations if c == 'ADD': reg[code[3]] = (reg[code[1]] + reg[code[2]]) % 2**256 @@ -174,7 +218,7 @@ def eval_contract(block,transaction_list,tx): 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 + 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': @@ -184,7 +228,7 @@ def eval_contract(block,transaction_list,tx): 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 + 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': @@ -278,7 +322,7 @@ def eval_contract(block,transaction_list,tx): ind = encode((reg[code[5]] + i) % 2**256,256,32) data.append(contract.get(ind)) tx = Transaction(0,to,value,fee,data) - tx.from = address + tx.sender = address transaction_list.insert(0,tx) elif code == 'DATA': reg[code[2]] = tx.data[reg[code[1]]] diff --git a/rlp.py b/rlp.py index 03272b6c7c..0abfffb4ae 100644 --- a/rlp.py +++ b/rlp.py @@ -17,14 +17,14 @@ def from_binary(b): else: return from_binary(b[:-1]) * 256 + ord(b[-1]) def __decode(s,pos=0): - if s == '': + if not s: return (None, 0) else: fchar = ord(s[pos]) - if fchar <= 24: + if fchar < 24: return (ord(s[pos]), pos+1) elif fchar < 56: - b = ord(s[pos]) - 24 + b = ord(s[pos]) - 23 return (from_binary(s[pos+1:pos+1+b]), pos+1+b) elif fchar < 64: b = ord(s[pos]) - 55 @@ -59,11 +59,13 @@ def decode(s): return __decode(s)[0] def encode(s): if isinstance(s,(int,long)): - if s <= 24: + if s < 0: + raise Exception("can't handle negative ints") + elif s >= 0 and s < 24: return chr(s) elif s <= 2**256: b = to_binary(s) - return chr(len(b) + 24) + b + return chr(len(b) + 23) + b else: b = to_binary(s) b2 = to_binary(len(b)) diff --git a/transactions.py b/transactions.py index ef457190b1..c7985bcb99 100644 --- a/transactions.py +++ b/transactions.py @@ -4,6 +4,7 @@ import re class Transaction(): def __init__(*args): + self = args[0] if len(args) == 2: self.parse(args[1]) else: @@ -17,24 +18,27 @@ class Transaction(): if re.match('^[0-9a-fA-F]*$',data): data = data.decode('hex') o = rlp.unparse(data) - self.nonce = decode(o[0],256) + self.nonce = o[0] self.to = o[1] - self.value = decode(o[2],256) - self.fee = decode(o[3],256) - self.data = rlp.unparse(o[4]) + self.value = o[2] + self.fee = o[3] + self.data = rlp.decode(o[4]) self.v = o[5] self.r = o[6] self.s = o[7] rawhash = sha256(rlp.encode([self.nonce,self.to,self.value,self.fee,self.data])) - self.from = hash160(ecdsa_raw_recover(rawhash,(self.v,self.r,self.s))) + pub = encode_pubkey(ecdsa_raw_recover(rawhash,(self.v,self.r,self.s)),'bin') + self.sender = bin_sha256(pub[1:])[-20:] + return self def sign(self,key): - rawhash = sha256(rlp.parse([self.to,self.value,self.fee,self.data])) + rawhash = sha256(rlp.encode([self.to,self.value,self.fee,self.data])) self.v,self.r,self.s = ecdsa_raw_sign(rawhash,key) - self.from = hash160(privtopub(key)) + self.sender = bin_sha256(privtopub(key)[1:])[-20:] + return self def serialize(self): - return rlp.parse([self.nonce, self.to, self.value, self.fee, self.data, self.v, self.r, self.s]) + return rlp.encode([self.nonce, self.to, self.value, self.fee, self.data, self.v, self.r, self.s]) def hex_serialize(self): return self.serialize().encode('hex') diff --git a/trie.py b/trie.py index 0b7ff1365d..93702fe2ab 100644 --- a/trie.py +++ b/trie.py @@ -164,6 +164,45 @@ class Trie(): if curnode[16]: total += 1 return total + def __to_dict(self,node): + if not node: return {} + curnode = rlp.decode(self.db.get(node)) + if not curnode: + raise Exception("node not found in database") + if len(curnode) == 2: + lkey = self.__decode_key(curnode[0]) + o = {} + if lkey[-1] == 16: + o[curnode[0]] = curnode[1] + else: + d = self.__to_dict(curnode[1]) + for v in d: + subkey = self.__decode_key(v) + totalkey = self.__encode_key(lkey+subkey) + o[totalkey] = d[v] + return o + elif len(curnode) == 17: + o = {} + for i in range(16): + d = self.__to_dict(curnode[i]) + for v in d: + subkey = self.__decode_key(v) + totalkey = self.__encode_key([i] + subkey) + o[totalkey] = d[v] + if curnode[16]: o[chr(16)] = curnode[16] + return o + else: + raise Exception("bad curnode! "+curnode) + + def to_dict(self,as_hex=False): + d = self.__to_dict(self.root) + o = {} + for v in d: + v2 = ''.join(['0123456789abcdef'[x] for x in self.__decode_key(v)[:-1]]) + if not as_hex: v2 = v2.decode('hex') + o[v2] = d[v] + return o + def get(self,key): key2 = ['0123456789abcdef'.find(x) for x in key.encode('hex')] + [16] return self.__get_state(self.root,key2)