Closed ppedziwiatr closed 1 year ago
done 💥
So I've been trying the KV implementation and it seems that there is an issue with its behavior, as the KV storages are shared on a contract basis. For a contract, if its KV storage has changed in interactWrite
s then when running readState
the storage will still contain the value set in the latest interactWrite
s
Hmm, in general any internal write on a contract with KV storage should end with an exception. As noted in the issue (https://github.com/warp-contracts/warp/issues/310), the KV storage currently does not support inner contract interactions (reads or writes) - https://github.com/warp-contracts/warp/issues/310
It should throw an error: either here https://github.com/warp-contracts/warp/blob/main/src/contract/HandlerBasedContract.ts#L76
or here https://github.com/warp-contracts/warp/blob/main/src/contract/HandlerBasedContract.ts#L538
If it is not throwing an error in your case - than we need to fix this in the first place - can you please share a MRE?
I wasn't mentioning internal writes though, I was just talking about simple interactWrite
calls. If I call interactWrite
and the interaction succeeds and sets something in the KV storage, when I call readState
and it evaluates the interaction, the storage will still have the values set by the interactWrite
, even before it's actually set in this current evaluation
I'm not sure I follow :-( what is a 'interactWrite call'? do you mean contract.writeInteraction
API in strict mode or sth like that?
Oops sorry, yes writeInteraction
is what I meant, I got the names confused...
Though now that I think about writeInteraction in a strict mode - in fact it may result in the issue that you've mentioned... Whenever an interaction is marked as a 'dryRun' - no caching should be made (that's how it works for standard, json states - we need to move this to the KV storage as well).
Good catch, will fix that soon! @Tadeuchi
@Tadeuchi , should be as easy as adding additional condition here: https://github.com/warp-contracts/warp/blob/main/src/legacy/smartweave-global.ts#L283 - with a check if !this._transaction.dryRun
(i.e. if it is a dryRun - do not commit the changes to the storage).
Will be fixed within https://github.com/warp-contracts/warp/issues/335
An alternative way for storing state for contracts.
NOTE Consider this as an experimental feature (though already used by our own contracts), with some constraints (see below).
Rationale
Imagine a PST contract with millions of entries in the 'balances' map. With the traditional - current way of storing a state - a change for one entry, requires passing to contract the whole state (the json) and then storing/caching it again - with only this one change. This is a big issue both from the storage perspective (we have to cache very similar, very big json's) and performance perspective - as the state needs to be deep-copied before each interaction - to assure a transactional processing (i.e. either all changes from a given interactions should be applied - or none, in case the contract would throw an Error).
Solution
This PR introduces an alternative way of storing a contract state - via a Key-Value storage (backed-up by default by the LevelDB). This allows to store and/or retrieve only required parts of the state within any given interaction - instead of having to process the whole BIG json each time.
API
The
SmartWeave
'global' object has 2 new methods added:SmartWeave.kv.put(key, value)
- allows to store a value (e.g. a wallet balance)SmartWeave.kv.get(key)
- allows to retrieve a value from the storage by its keyThe
Contract
interface has a new method added:getStorageValues(keys: string[]): Promise<SortKeyCacheResult<Map<string, any>>>
- which allows to check the latest values for the passed array of keys.All the changes in the KV storage done within one given interaction are atomic - either all will be applied, or none (in case of an
ContractError
) - this is handled by thecommit
/rollback
mechanism.Enabling KV storage for contract
In order to use a KV storage in the contract, either:
the contract's manifest must be specified during deployment - with a
useKVStorage
option set totrue
:the
evaluationOptions
used for contract evaluation must have theuseKVStorage
option set true, e.g.:NOTE with the KV Storage, the 'traditional', json-based state can be still used for storing some of the data (e.g. the name and the ticker of the PST)
Example usage in the contract
Examples of using the storage in the contract code (in a standard, PST
transfer
method):The full contract example is available here: https://github.com/warp-contracts/warp/blob/24c47c01c5f85734e5be69a18bd3092e2a99c1a7/src/__tests__/integration/data/kv-storage.js
Example of retrieving the values via SDK
Constraints
Currently - when using KV storage - no interactions with foreign contracts are allowed. Such support will be added in the future PRs. The bindings for the Rust are not yet implemented - will be added in https://github.com/warp-contracts/warp/issues/330
Implementation details
SmartWeave._activeTx
).SortKeyCache
interface. By default it is using the LevelDB (with a file based storage for node.js env and IndexedDB storage for browser env)SortKeyCache
interface can be used (e.g. https://github.com/warp-contracts/warp-contracts-lmdb#warp-contracts-lmdb-cache). In order to configure custom storage - use thewarp.useKVStorageFactory
method, e.g.:NOTE the cache for each contract has to be stored in a separate db file - that's why the
useKVStorageFactory
has thecontractTxId
as an argument.