diff --git a/blocks.py b/blocks.py index ee519ca8cf..de1df78633 100644 --- a/blocks.py +++ b/blocks.py @@ -2,35 +2,38 @@ from pybitcointools import * import rlp import re from transactions import Transaction +from trie import Trie 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)] + + header, transaction_list, self.siblings = rlp.decode(data) + [ number, + self.prevhash, + self.siblings_root, + self.coinbase, + state_root, + self.transactions_root, + diff, + timestamp, + 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.state = Trie('statedb',state_root) + # 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 self.state.root != '' and self.state.__get_state(self.state.root,[]) == '': + raise Exception("State Merkle root not found in database!") if bin_sha256(transaction_list) != transactions_root: raise Exception("Transaction list root hash does not match!") if bin_sha256(sibling_list) != sibling_root: @@ -38,148 +41,96 @@ class Block(): for sibling in self.siblings: if sibling[0] != self.prevhash: raise Exception("Sibling's parent is not my parent!") + # TODO: check POW - - 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 + 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: - # 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 + 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: + return False + sender_value = decode(sender_state[1],256) + if sender_value < fee: + return False + sender_state[1] = encode(sender_value - fee,256) + 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) + self.state.update(self.coinbase,miner_state) + return True - 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_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) 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) + state = rlp.decode(self.state.get(address)) + return decode(state[1] || '',256) - 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) + # 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 + return Trie('statedb',state[2]) - 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 update_contract(self,address,contract): + state = rlp.decode(self.state.get(address)) + if not state or decode(state[0],256) == 0: return False + state[2] = contract.root + self.state.update(address,state) - 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)) - + # Serialization method; should act as perfect inverse function of the constructor + # assuming no verification failures 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]) + txlist = [x.serialize() for x in self.transactions] + header = [ encode(self.number,256), + self.prevhash, + bin_sha256(rlp.encode(self.siblings)), + self.coinbase, + self.state.root, + bin_sha256(rlp.encode(self.txlist)), + encode(self.difficulty,256), + encode(self.timestamp,256), + encode(self.nonce,256), + self.extra ] + return rlp.encode([header, txlist, self.siblings ]) + + def hash(self): + return bin_sha256(self.serialize()) diff --git a/processblock.py b/processblock.py index 4fb7700890..6f3eff24f1 100644 --- a/processblock.py +++ b/processblock.py @@ -1,5 +1,6 @@ from transactions import Transaction from blocks import Block +import time scriptcode_map = { 0x00: 'STOP', @@ -22,11 +23,11 @@ scriptcode_map = { 0x31: 'RIPEMD-160', 0x32: 'ECMUL', 0x33: 'ECADD', - 0x34: 'SIGN', - 0x35: 'RECOVER', + 0x34: 'ECSIGN', + 0x35: 'ECRECOVER', 0x40: 'COPY', 0x41: 'STORE', - 0x42: 'LD', + 0x42: 'LOAD', 0x43: 'SET', 0x50: 'JMP', 0x51: 'JMPI', @@ -37,73 +38,125 @@ scriptcode_map = { 0x71: 'RAWTX', 0x80: 'DATA', 0x81: 'DATAN', - 0x90: 'MYADDRESS' + 0x90: 'MYADDRESS', + 0x91: 'BLKHASH', + 0xff: 'SUICIDE' } -fees = { - 'stepfee': 2**60 * 8192, +params = { + 'stepfee': 2**60 * 4096, 'txfee': 2**60 * 524288, - 'memoryfee': 2**60 * 262144 + 'memoryfee': 2**60 * 262144, + 'datafee': 2**60 * 16384, + 'cryptofee': 2**60 * 65536, + 'extrofee': 2**60 * 65536, + 'blocktime': 60, + 'period_1_reward': 2**80 * 1024, + 'period_1_duration': 57600, + 'period_2_reward': 2**80 * 512, + 'period_2_duration': 57600, + 'period_3_reward': 2**80 * 256, + 'period_3_duration': 57600, + 'period_4_reward': 2**80 * 128 } -def eval_tx(block): - tx = block.transactions.pop(0) - oldbalance = block.get_balance(tx.from) - debit = tx.value + tx.fee - if tx.to == '': - debit += fees['memoryfee'] * len(filter(lambda x:x > 0,tx.data)) - if oldbalance < debit: - return - block.update_balance(tx.from,oldbalance - debit) - if tx.to == '': - mk_contract(block,tx) #todo: continue here +def eval(block,transactions,timestamp,coinbase): + h = block.hash() + # Process all transactions + while len(transactions) > 0: + tx = transactions.pop(0) + fee = params['txfee'] + len(tx.data) * params['datafee'] + if tx.to = '\x00'*20: + fee += len(tx.data) * params['memoryfee'] + # 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) + # Pay miner fee + miner_state = rlp_decode(self.state.get(self.coinbase)) or ['','',''] + miner_balance = decode(miner_state[1],256) + block.number += 1 + reward = 0 + if block.number < params['period_1_duration']: + reward = params['period_1_reward'] + elif block.number < params['period_2_duration']: + reward = params['period_2_reward'] + elif block.number < params['period_3_duration']: + reward = params['period_3_reward'] else: - block.update_balance(tx.to,block.get_balance(tx.to) + tx.value) - if block.get_contract(tx.to) != 0: - eval_contract(block,tx) + reward = params['period_4_reward'] + miner_balance += reward + for sibling in block.siblings: + sib_miner_state = rlp_decode(self.state.get(sibling[3])) + sib_miner_state[1] = encode(decode(sib_miner_state[1],256)+reward*7/8,256) + self.state.update(sibling[3],sib_miner_state) + miner_balance += reward/8 + miner_state[1] = encode(miner_balance,256) + self.state.update(self.coinbase,miner_state) + # Check timestamp + if timestamp < block.timestamp or timestamp > int(time.time()) + 3600: + raise new Exception("timestamp not in valid range!") + # Update difficulty + if timestamp >= block.timestamp + 42: + block.difficulty += int(block.difficulty / 1024) + else: + block.difficulty -= int(block.difficulty / 1024) + block.prevhash = h + block.coinbase = coinbase + block.transactions = [] + block.siblings = [] + return block -def mk_contract(block,tx): - cdata = tx.data - # todo: continue here - - -def eval_contract(block,tx): +def eval_contract(block,transaction_list,tx): address = tx.to # Initialize registers reg = [0] * 256 - reg[0] = decode(tx.from,16) - reg[1] = decode(tx.to,16) + reg[0] = decode(tx.from,256) + reg[1] = decode(tx.to,256) 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]]) + contract = block.get_contract(address) + if not contract: + return while 1: + # 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) ] + # Invalid code instruction or STOP code stops execution sans fee + if val_at_index >= 256**6 or code[0] == 'STOP': + break # Calculate fee - totalfee = 0 + minerfee = 0 + nullfee = 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) ] + minerfee += params["stepfee"] c = scriptcode_map[code[0]] + if c in ['STORE','LOAD']: + minerfee += params["datafee"] + if c in ['EXTRO','BALANCE']: + minerfee += params["extrofee"] + if c in ['SHA256','RIPEMD-160','ECMUL','ECADD','ECSIGN','ECRECOVER']: + minerfee += params["cryptofee"] 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 reg[code[1]] != 0: nullfee += params["memoryfee"] + if existing: nullfee -= params["memoryfee"] - if c == 'STOP': + # If we can't pay the fee, break, otherwise pay it + if block.get_balance(address) < minerfee + nullfee: break - elif c == 'ADD': + block.pay_fee(address,nullfee,False) + block.pay_fee(address,minerfee,True) + + # Evaluate operations + if 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 @@ -166,25 +219,28 @@ def eval_contract(block,tx): elif code == 'ECADD': pt1 = (reg[code[1]],reg[code[2]]) pt2 = (reg[code[3]],reg[code[4]]) + # Invalid point 1 if (pt1[0] ** 3 + 7 - pt1[1] ** 2) % N != 0: reg[code[5]], reg[code[6]] = 0,0 + # Invalid point 2 elif (pt2[0] ** 3 + 7 - pt2[1] ** 2) % N != 0: reg[code[5]], reg[code[6]] = 0,0 + # Legitimate points else: pt3 = base10_add(pt1,pt2) reg[code[5]], reg[code[6]] = pt3[0], pt3[1] - elif code == 'SIGN': + elif code == 'ECSIGN': reg[code[3]], reg[code[4]], reg[code[5]] = ecdsa_raw_sign(reg[code[1]],reg[code[2]]) - elif code == 'RECOVER': + elif code == 'ECRECOVER': 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)) + contract.update(encode(reg[code[2]],256,32),reg[code[1]]) + elif code == 'LOAD': + reg[code[2]] = contract.get(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': @@ -194,12 +250,18 @@ def eval_contract(block,tx): elif code == 'IND': reg[code[1]] = index elif code == 'EXTRO': - address = encode(reg[code[1]] % 2**160,256,20) - field = encode(reg[code[2]] - reg[code[3]] = block.get_contract_state(address,field) + if reg[code[1]] >= 2**160: + reg[code[3]] = 0 + else: + address = encode(reg[code[1]],256,20) + field = encode(reg[code[2]]) + reg[code[3]] = block.get_contract(address).get(field) elif code == 'BALANCE': - address = encode(reg[code[1]] % 2**160,256,20) - reg[code[2]] = block.get_balance(address) + if reg[code[1]] >= 2**160: + reg[code[2]] = 0 + else: + address = encode(reg[code[1]],256,20) + reg[code[2]] = block.get_balance(address) elif code == 'MKTX': to = encode(reg[code[1]],256,32) value = reg[code[2]] @@ -211,20 +273,23 @@ def eval_contract(block,tx): data = [] for i in range(datan): ind = encode((reg[code[5]] + i) % 2**256,256,32) - data.append(block.get_contract_state(address,ind)) - tx = Transaction(to,value,fee,data) + data.append(contract.get(ind)) + tx = Transaction(0,to,value,fee,data) tx.from = address - block.transactions.append(tx) + transaction_list.insert(0,tx) elif code == 'DATA': reg[code[2]] = tx.data[reg[code[1]]] elif code == 'DATAN': reg[code[1]] = len(tx.data) elif code == 'MYADDRESS': reg[code[1]] = address + elif code == 'BLKHASH': + reg[code[1]] = decode(block.hash()) elif code == 'SUICIDE': - sz = block.get_contract_size(address) - negfee = sz * fees["memoryfee"] - toaddress = encode(reg[code[1]],256,32) - block.update_balance(toaddress,block.get_balance(toaddress) + negfee) - block.update_contract(address,0) + sz = contract.get_size() + negfee = -sz * params["memoryfee"] + toaddress = encode(reg[code[1]],256,20) + block.pay_fee(roaddress,negfee,False) + contract.root = '' break + block.update_contract(address,contract) diff --git a/rlp.py b/rlp.py index 708c770d3e..7bc5db2668 100644 --- a/rlp.py +++ b/rlp.py @@ -25,6 +25,7 @@ def num_to_var_int(n): return ''.join([chr(x) for x in s]) def __decode(s): + if s == '': return None o = [] index = [0] def read_var_int(): diff --git a/transactions.py b/transactions.py index 0754ea1084..ef457190b1 100644 --- a/transactions.py +++ b/transactions.py @@ -7,32 +7,37 @@ class Transaction(): if len(args) == 2: self.parse(args[1]) else: - self.to = args[1] - self.value = args[2] - self.fee = args[3] - self.data = args[4] - if len(args) > 5: self.sig = args[5] - - def lpad(inp,L): return '\x00' * max(0,L - len(inp)) + inp + self.nonce = args[1] + self.to = args[2] + self.value = args[3] + self.fee = args[4] + self.data = args[5] 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))) + self.nonce = decode(o[0],256) + self.to = o[1] + self.value = decode(o[2],256) + self.fee = decode(o[3],256) + self.data = rlp.unparse(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))) + 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])) + self.v,self.r,self.s = ecdsa_raw_sign(rawhash,key) + self.from = hash160(privtopub(key)) + def serialize(self): - return rlp.parse([self.to, self.value, self.fee, self.data, self.sig]).encode('hex') + return rlp.parse([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') + def hash(self): return bin_sha256(self.serialize()) diff --git a/trie.py b/trie.py index d7eb900c45..0b7ff1365d 100644 --- a/trie.py +++ b/trie.py @@ -12,11 +12,15 @@ class DB(): def put(self,key,value): return self.db.Put(key,value) def delete(self,key): return self.db.Delete(key) +databases = {} + class Trie(): - def __init__(self,db,root='',debug=False): + def __init__(self,dbfile,root='',debug=False): self.root = root - self.db = DB(db) self.debug = debug + if dbfile not in databases: + databases[dbfile] = DB(dbfile) + self.db = databases[dbfile] def __encode_key(self,key): term = 1 if key[-1] == 16 else 0 @@ -144,10 +148,28 @@ class Trie(): newnode2 = newnode return self.__put(newnode2) + def __get_size(self,node): + if not node: return 0 + curnode = self.db.get(node) + if not curnode: + raise Exception("node not found in database") + if len(curnode) == 2: + key = self.__decode_key(curnode[0]) + if key[-1] == 16: return 1 + else: return self.__get_size(curnode[1]) + elif len(curnode) == 17: + total = 0 + for i in range(16): + total += self.__get_size(curnode[i]) + if curnode[16]: total += 1 + return total + def get(self,key): key2 = ['0123456789abcdef'.find(x) for x in key.encode('hex')] + [16] return self.__get_state(self.root,key2) + def get_size(self): return self.__get_size(self.root) + def update(self,key,value): if not isinstance(key,str) or not isinstance(value,str): raise Exception("Key and value must be strings")