polkascan / py-substrate-interface

Python Substrate Interface
https://polkascan.github.io/py-substrate-interface/
Apache License 2.0
240 stars 114 forks source link

query() returns default storage item instead of None #177

Closed sergpsu closed 2 years ago

sergpsu commented 2 years ago

I have the folowing call:

res = isubstrate.query( module='myPallet', storage_function='myStorageMap', params=[ nonExistingStorageKey ] )

Since key does not exist in storage I expect None to be returned. But the returning value is an impementation of Default trait for my storage value which actually would be fine unless the following. The actual storage value is a struct with the only field of AccountId type and Default implementation fills that field with all zeroes 0x0000000... So if query() call uses default value I expect to receive "000000000...000" hex string but instead "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" is returned. So finally there is no way to recognize that storage key was not found at all.

arjanz commented 2 years ago

Is the storage modifier in the metadata for your storage call Optional? (like for example System.ExtrinsicCount) Then it should return None if no result is found. If the storage modifier is Default (like System.Account) it should return the default value if no result is found and this is expected behaviour.

You can check the metadata properties of a storage function with: substrate.get_metadata_storage_function("System", "ExtrinsicCount")

sergpsu commented 2 years ago

Thanks! Is there a simple way to know that the default value is returned ( beside copying query() inner logic )?

arjanz commented 2 years ago

Yes there is, its a property of the result of the get_metadata_storage_function() function as I mentioned above..

sergpsu commented 2 years ago

I mean how can I know that query() returned default object. Currently I've implemented this by creating default object by myself and comparing it to the query() result, but it does not look too elegant

arjanz commented 2 years ago

Ok maybe it was a bit confusing (or I misinterpreted the question :) ) but here is an example for storage function System.Account:

storage_function = substrate.get_metadata_storage_function("System", "Account")

if storage_function.value['modifier'] == 'Default':
    # query() will return default value when no result
    result = substrate.query("System", "Account")

if storage_function.value['modifier'] == 'Optional':
    # query() will return None when no result
    result = substrate.query("System", "Account")

Is this what you mean?

sergpsu commented 2 years ago

No :) Let me explain in detail. The query(.. param=[somekey]) call returns me such object:

{"account_id":"5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"} 

The problem is that by "looking" at the object I can't say that this is the default storage item, so I can't tell that storage does not contain somekey. As I wrote above I solved it by constructing object from metadata default value and comparing result with that object, but maybe there is more elegant way to tell that "storage key not found, default object is returned"

arjanz commented 2 years ago

Ok I finally get it now 😁

That is indeed valuable information that gets lost now after the query result is returned.

I am considering two solutions, one is to add to the result obj if a result was found, second is a kwarg for query() to exclitly tell the function to not return the default value but None on no result.

I think the kwarg is more elegant and in this case no unnecessary extra decoding is done.

sergpsu commented 2 years ago

Yes, I like kwarg option too.

sergpsu commented 2 years ago

Actually 1st option might make sense too in a situation when having default object is fine but also need to know that it's default indeed.

arjanz commented 2 years ago

Released since v1.1.8