pythondictionaryethereumcomplex-data-typesserpent

How to store "complex" data structure as a Persistent Data Structures using ethereum & serpent


My work is concerning Smart Contract dev. using (py)ethereum and serpent,

When reading "A Programmer’s Guide to Ethereum and Serpent", I saw on point 5.9 that :

[...] Persistent data structures can be declared using the data declaration. This allows for the declaration of arrays and tuples. [...]

and:

[...] For simple storage, self.storage[] is useful, but for larger contracts, we recommend the use of data (unless you need a key- value storage, of course) [...]

Code example:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import serpent
from ethereum import tester, utils, abi

serpent_code = '''
data mystorage[]


def test_data_storage(key,value):
    if not self.mystorage[key]:
        self.mystorage[key]=value
        return(1)
    else:
        return(0)


def get_value_mystorage(key):
    if not self.mystorage[key]:
        return(0)
    else:
        return(self.mystorage[key])



def test_self_storage(key,value):
    if not self.storage[key]:
        self.storage[key]=value
        return(1)
    else:
        return(0)


def get_value_self_storage(key):
    if not self.storage[key]:
        return(0)
    else:
        return(self.storage[key])

'''


s = tester.state()
c = s.abi_contract(serpent_code)

#example with self storage
c.test_self_storage("keyA",1)
print c.get_value_self_storage("keyA") #store and access data works in self.storage!

#example with mystorage
c.test_data_storage("keyB",2)
print c.get_value_mystorage("keyB") #store and access data works in data as persistant data storage!

#fail example with complex data
my_complex_data={"keyA":1,"keyB":2, "keyC":[1,2,3], "keyD":{"a":1,"b":2}}
c.test_data_storage("keyComplex",my_complex_data) 

#don't store anything because error:
# ethereum.abi.ValueOutOfBounds: {'keyC': [1, 2, 3], 'keyB': 2, 'keyA': 1, 'keyD': {'a': 1, 'b': 2}}

My question is : what is the best way and how to store complex data (see my_complex_data variable in code) like a dictionary which contain others dict. (or arrays as key value) as a persistant data structures ?

and does someone know if it is possible and how to store any class structure as a persistant data structures ?


Solution

  • IMPORTANT : Please note that according to this Vitalik Tweet, Serpent is now an "outdated tech".

    The Serpent README has been updated to :

    Being a low-level language, Serpent is NOT RECOMMENDED for building applications unless you really really know what you're doing. The creator recommends Solidity as a default choice, LLL if you want close-to-the-metal optimizations, or Vyper if you like its features though it is still experimental.

    If you want to code Ethereum contracts from Python in order to release a production product, start to think about to migrate to Solidity or Vyper (which is still a "New experimental programming language")


    Concerning my question, I finaly found a (tricky/dirty) solution, which consist of encoding the complex data before pushing it to the persistant data storage, then decoding after retrieving data from the storage.

    Please see updated code bellow:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    
    import serpent
    import json,math
    from ethereum import tester, utils, abi
    
    serpent_code = '''
    data mystorage[]
    
    
    def test_data_storage(key,value):
        if not self.mystorage[key]:
            self.mystorage[key]=value
            return(1)
        else:
            return(0)
    
    
    def get_value_mystorage(key):
        if not self.mystorage[key]:
            return(0)
        else:
            return(self.mystorage[key])
    
    
    
    def test_self_storage(key,value):
        if not self.storage[key]:
            self.storage[key]=value
            return(1)
        else:
            return(0)
    
    
    
    def rebuild_complex_data_storage(key):
        if not self.storage[key]:
            return(0)
    
    def get_value_self_storage(key):
        if not self.storage[key]:
            return(0)
        else:
            return(self.storage[key])
    
    
    def put_complex_data_storage(key,value=""):
        self.storage[key]=value
    
    
    def get_complex_data_storage(key):
        return(self.storage[key])
    
    
    
    '''
    
    
    s = tester.state()
    c = s.abi_contract(serpent_code)
    
    #example with self storage
    c.test_self_storage("keyA",1)
    print c.get_value_self_storage("keyA") #store and access data works in self.storage!
    
    #example with mystorage
    c.test_data_storage("keyB",2)
    print c.get_value_mystorage("keyB") #store and access data works in data as persistant data storage!
    
    start_block="99699"
    
    def block_chain_encode(c,key,my_complex_data):
    
        #ENCODE PART
    
        global start_block
    
        print "\n","*","ENCODE","*"
    
        #fail example with complex data
        my_complex_data_json_format=unicode(json.dumps(my_complex_data))
    
        int_str=start_block
        for each in my_complex_data_json_format:
            int_str=int_str+ str(ord(each)).zfill(4)
        int_str=int_str+start_block
        print int_str
        #toHex = lambda x:"".join([hex(ord(c))[2:].zfill(2) for c in x])
        print "need to declare 32 bit words", math.ceil(len(int_str)/32.),"times"
        #max 32 char for each key
    
        max_size=int(math.ceil(len(int_str)/32.))
        for i in range(0,max_size):
            block_content=int_str[i*32:(i+1)*32]
            print "block",i," content",block_content
            if i==0:
                c.test_data_storage("keyComplex_len",max_size)
    
            key="keyComplex_"+str(i).zfill(3)
            res=c.test_data_storage(key,int(block_content)) #store block part in block chain
            print " - substorage",key,"",
    
        #print "data storage part is done !"
        return 1
    
    def block_chain_decode(c,key):
    
        #DECODE PART
    
        global start_block
        print "\n","*","DECODE","*"
    
    
        int_str=""
        lenght= c.get_value_mystorage("keyComplex_len") #get lenght to know how many block we need to get for all data from block chain
        for i in range(0,lenght):
            key="keyComplex_"+str(i).zfill(3)
            print key,c.get_value_mystorage(key)
            int_str=int_str+str(c.get_value_mystorage(key))
    
        content_=""
        sp_int_str= int_str.split(start_block)
        if len(sp_int_str)==3:
            if sp_int_str[-1]==sp_int_str[0] and sp_int_str[0]=="":
                print "ok"
                content_=sp_int_str[1]
    
        js_str=""
        if content_!="":
            for i in range(0,int(len(content_)/4.)):
                js_str+=chr(int(content_[i*4:(i+1)*4])) #recover char from asci code
        my_complex_data=json.loads(js_str)
    
        return my_complex_data
    
    #define complex data
    my_complex_data={"keyA":1,"keyB":2, "keyC":[1,2,3], "keyD":{"a":1,"b":2}}
    print "Initial complex data",my_complex_data
    
    #encode and push to block chain
    block_chain_encode(c,"keyComplex",my_complex_data)
    
    #get complex variable from block chain
    my_complex_data_back = block_chain_decode(c,"keyComplex")
    #acces data
    print my_complex_data_back["keyD"]["a"]+my_complex_data_back["keyD"]["b"]
    print "Extracted complex data from blockchain",my_complex_data
    print "Integrity test pass:",my_complex_data_back==my_complex_data
    

    Which return:

    1
    2
    Initial complex data {'keyC': [1, 2, 3], 'keyB': 2, 'keyA': 1, 'keyD': {'a': 1, 'b': 2}}
    
    * ENCODE *
    99699012300340107010101210067003400580032009100490044003200500044003200510093004400320034010701010121006600340058003200500044003200340107010101210065003400580032004900440032003401070101012100680034005800320123003400970034005800320049004400320034009800340058003200500125012599699
    need to declare 32 bit words 9.0 times
    block 0  content 99699012300340107010101210067003
     - substorage keyComplex_000  block 1  content 40058003200910049004400320050004
     - substorage keyComplex_001  block 2  content 40032005100930044003200340107010
     - substorage keyComplex_002  block 3  content 10121006600340058003200500044003
     - substorage keyComplex_003  block 4  content 20034010701010121006500340058003
     - substorage keyComplex_004  block 5  content 20049004400320034010701010121006
     - substorage keyComplex_005  block 6  content 80034005800320123003400970034005
     - substorage keyComplex_006  block 7  content 80032004900440032003400980034005
     - substorage keyComplex_007  block 8  content 8003200500125012599699
     - substorage keyComplex_008  
    * DECODE *
    keyComplex_000 99699012300340107010101210067003
    keyComplex_001 40058003200910049004400320050004
    keyComplex_002 40032005100930044003200340107010
    keyComplex_003 10121006600340058003200500044003
    keyComplex_004 20034010701010121006500340058003
    keyComplex_005 20049004400320034010701010121006
    keyComplex_006 80034005800320123003400970034005
    keyComplex_007 80032004900440032003400980034005
    keyComplex_008 8003200500125012599699
    ok
    3
    Extracted complex data from blockchain {'keyC': [1, 2, 3], 'keyB': 2, 'keyA': 1, 'keyD': {'a': 1, 'b': 2}}
    Integrity test pass: True