Grid
The Grid function is the core operation in the entire Loom system. The primary function of the Grid is to regulate usage of various limited resources. One of those limited resources is the disk space used by the Grid itself! Consequently you must pay one usage token to buy a grid location. If you sell a grid location back to the system, you receive a refund of one usage token.
The Grid administrator possesses the issuing location for usage tokens, and can thus regulate the supply of those tokens based on supply and demand for Grid resources.
Grid Terminology
An id is a 128-bit number expressed as a hexadecimal string, All hexadecimal strings must use lower-case letters 'a'-'f', not the upper-case letters 'A'-'F'. Example:
3a4511c895fc71fdfea08f3f3f8c7a97
A hash is a 256-bit number expressed as a hexadecimal string. Example:
6486b8158425d380642a444f5b2047fd97e452f7c92eaa8d8a7d2c53516d4a69
A quantity is a signed integer value with 128 bits of precision, expressed in decimal notation. The largest positive value is:
170141183460469231731687303715884105727
The smallest negative value is:
-170141183460469231731687303715884105728
A type is an id which represents a distinct "asset type." These asset types may be created at will for various purposes. There is one special-purpose asset type which represents usage tokens, the zero type:
00000000000000000000000000000000
A location is an id which represents a distinct "place" within a given asset type, where units of that asset type may be stored.
You may think of the grid as a whole as a giant 2-dimensional array. The rows and columns of this array are numbered from 0 to 2^128-1. Inside each cell of this array is a signed 128-bit integer quantity (a 2's-complement number).
The Grid
Row/Col | 0 | 1 | ... | 2^128-1 |
0 | ||||
1 | ||||
... | ||||
2^128-1 |
Within each row (asset type) there is always exactly one location with a negative value. Initially there is a -1 at location 0 of every row. The negative location within a row is known as the issuer location for that row. The issuer location is the only place from which new units of the asset type can be created. Within every other location in a row, the value is either 0 or positive.
The grid operations maintain conservation of value. The sum of the values within each row is always precisely -1.
To "create" a new asset type, you simply choose a row at random and move the issuer location from its original place at location 0 to some other random location you choose. Now you control that asset type entirely, since you are the only entity which knows the issuer location for that type. You can issue units at will (at most 2^128-1 of them) and move them anywhere you like.
The grid therefore acts somewhat like a giant spreadsheet, albeit with some very restricted arithmetic properties. The grid is nothing more than a very solid and well-defined counting mechanism.
The beauty of the grid concept is that it allows us to build a web site starting from nothing but an empty grid, and then construct all sorts of content management applications from there, where the usage of limited resources such as disk space is strictly regulated and throttled by the numbers in the grid. For example, if we decided to provide a feature to upload pictures for safekeeping, the web site could charge a certain number of usage tokens per kilobyte of data uploaded.
If you don't do things this way, with some form of economic regulation, then a web site can become a giant "tragedy of the commons" with no sensible limits on usage. The traditional way most sites deal with this is to create a revenue model based on advertising. Perhaps the Loom method is a refreshing way for users to avoid all the ads, and providers to have a new revenue model.
Example API Call
Here is a url which executes a "Move" operation, moving 42500 units of type 0f8fccf7c65e42d422c37ea222e700d5 from the origin location c80481226973dfec6dc06c8dfe8a5b1c to the destination location 29013fc66a653f6765151dd352675d0f.
https://fexl.com/loom?function=grid&action=move &type=0f8fccf7c65e42d422c37ea222e700d5 &qty=42500 &orig=c80481226973dfec6dc06c8dfe8a5b1c &dest=29013fc66a653f6765151dd352675d0f
(The url is broken up across lines for clarity. In practice you would keep the entire url on a single line.)
When the API receives this GET operation from the internet, it translates the information into an internal key-value structure for easy access. It then executes the operation, which has the effect of filling out additional keys and values in the key-value structure. Then it sends the results back to the caller as a plain-text document, with the resulting key-value structure expressed in our simple standard "KV" format.
For example, after the url above is translated and executed, the API will send the result back to the caller like this:
Content-Type: text/plain ( :function =grid :action =move :type =0f8fccf7c65e42d422c37ea222e700d5 :qty =42500 :orig =c80481226973dfec6dc06c8dfe8a5b1c :dest =29013fc66a653f6765151dd352675d0f :status =success :value_orig =0 :value_dest =42500 )
Grid Operations
Here we describe the API (application programming interface) for the Grid. The operations are Buy, Sell, Issuer, Touch, Look, and Move.
Buy
Buy a location in the grid. This costs one usage token. The input is:
Key | Value | Description |
---|---|---|
function | grid | |
action | buy | Name of operation |
type | id | Asset type |
loc | id | Location to buy |
usage | id | Location of usage tokens (where 1 is charged) |
If the Buy operation succeeds, you will see the following keys in the result:
Key | Value | Description |
---|---|---|
status | success | Everything went well. |
value | qty | The value at the new location. This will typically be 0, unless you are buying the location 0 for a brand new type, in which case it will be -1. |
usage_balance | qty | New usage balance |
If the operation fails, the status will be fail, and the reason for the failure will be indicated with any keys below which apply:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error_type | not_valid_id | type is not a valid id |
error_loc | not_valid_id | loc is not a valid id |
error_usage | not_valid_id | usage is not a valid id |
error_loc | occupied | loc is already occupied (bought) |
error_usage | insufficent | usage location did not have at least one usage token |
Sell
Sell a location in the grid. This refunds one usage token. The input is:
Key | Value | Description |
---|---|---|
function | grid | |
action | sell | Name of operation |
type | id | Asset type |
loc | id | Location to sell |
usage | id | Location of usage tokens (where 1 is refunded) |
If the Sell operation succeeds, you will see the following keys in the result:
Key | Value | Description |
---|---|---|
status | success | Everything went well. |
value | qty | The value at the location. You can only sell a location that is empty: either a 0 value in a non-zero location, or a -1 value in a zero location. These are the values which can be safely deleted from the database without losing any information. |
usage_balance | qty | New usage balance |
If the operation fails, the status will be fail, and the reason for the failure will be indicated with any keys below which apply:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error_type | not_valid_id | type is not a valid id |
error_loc | not_valid_id | loc is not a valid id |
error_usage | not_valid_id | usage is not a valid id |
error_loc | vacant | loc is already vacant (sold) |
error_loc | non_empty | loc is not empty |
error_loc | cannot_refund | Cannot sell a usage token location and receive the refund in same place. |
Issuer
Change the issuer location for an asset type. The input is:
Key | Value | Description |
---|---|---|
function | grid | |
action | issuer | Name of operation |
type | id | Asset type |
orig | id | Current issuer location |
dest | id | New issuer location |
If the Issuer operation succeeds, you will see the following keys in the result:
Key | Value | Description |
---|---|---|
status | success | Everything went well. |
value_orig | qty | Value of original location (after the change) |
value_dest | qty | Value of destination location |
If the operation fails, the status will be fail, and the reason for the failure will be indicated with any keys below which apply:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error_type | not_valid_id | type is not a valid id |
error_orig | not_valid_id | orig is not a valid id |
error_dest | not_valid_id | dest is not a valid id |
error_qty | insufficient | insufficient value at orig/dest (or other error listed below) |
The operation must obey the following rules (otherwise you'll see error_qty = insufficient):
- orig and dest are distinct (i.e. locations are not identical)
- orig and dest exist (i.e. the locations have been previously bought).
- orig value is negative (i.e. the issuer location).
- dest value is zero (so you aren't clobbering an existing value).
Touch
Examine a location directly, seeing the value stored there.
(Note that there is another operation called "Look" which examines a location indirectly using a hash, e.g. for audit purposes. See below.)
The input for the Touch operation is:
Key | Value | Description |
---|---|---|
function | grid | |
action | touch | Name of operation |
type | id | Asset type |
loc | id | Location |
If the Touch operation succeeds, you will see the following keys in the result:
Key | Value | Description |
---|---|---|
status | success | Everything went well. |
value | qty | Value at location |
If the operation fails, the status will be fail, and the reason for the failure will be indicated with any keys below which apply:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error_type | not_valid_id | type is not a valid id |
error_loc | not_valid_id | loc is not a valid id |
error_loc | vacant | loc is vacant (not yet bought) |
Look
Examine a location indirectly, seeing the value stored there.
The Look operation examines a location indirectly using a hash. Using the hash, one may view the contents of a location without the ability to change them.
For example, you could safely divulge the hash of a location to another party for audit purposes. This would give the auditor the ability to monitor the location, but not to change it in any way. If all you have is the hash of a location, then you can Look but you can't Touch.
The input for the Look operation is:
Key | Value | Description |
---|---|---|
function | grid | |
action | look | Name of operation |
type | id | Asset type |
hash | hash | Hash of location to examine |
If the Look operation succeeds, you will see the following keys in the result:
Key | Value | Description |
---|---|---|
status | success | Everything went well. |
value | qty | Value at location |
If the operation fails, the status will be fail, and the reason for the failure will be indicated with any keys below which apply:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error_type | not_valid_id | type is not a valid id |
error_hash | not_valid_hash | hash is not a valid hash |
error_loc | vacant | location is vacant (not yet bought) |
Move
Move units of a given type from one location to another.
The input for the Move operation is:
Key | Value | Description |
---|---|---|
function | grid | |
action | move | Name of operation |
type | id | Asset type |
qty | qty | Number of units to move |
orig | id | Origin location (subtract qty from here) |
dest | id | Destination location (add qty to here) |
The quantity is normally positive, but a negative or zero value is also allowed.
The Loom system first tries subtracting the qty from the origin and adding it to the destination. If the sign (+/-) of both the origin and destination remain unchanged, then the operation succeeds and the database is updated. Otherwise, if either sign changes, the operation fails and the database is unaffected.
In short, if both locations were positive or zero before the move, then they must remain so after the move.
Now consider the case where one of the locations is negative. That location is the privileged issuer location for the asset type, and in this case the move operation is either creating or destroying units of the asset type.
If the issuer location becomes more negative, units are being created at the other location. If the issuer location becomes less negative, units are being destroyed at the other location. The same rule applies here as well: if a location is negative before the move, it must remain negative after the move as well.
This simple rule, called sign preservation, enforces the principle of conservation of value within the Loom grid. Note also that this rule ensures that there can be only one issuer location (negative value) for a given type. This rule also prevents the negative value from moving elsewhere during a move. If you wish to move the issuer location, you must use the Issuer operation (see above).
If the Move operation succeeds, you will see the following keys in the result:
Key | Value | Description |
---|---|---|
status | success | Everything went well. |
value_orig | qty | New value at origin |
value_dest | qty | New value at destination |
If the operation fails, the status will be fail, and the reason for the failure will be indicated with any keys below which apply:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error_type | not_valid_id | type is not a valid id |
error_orig | not_valid_id | orig is not a valid id |
error_dest | not_valid_id | dest is not a valid id |
error_qty | not_valid_int | qty is not a valid integer value |
error_qty | insufficient | insufficient value at orig/dest (or other error listed below) |
Note that if the orig or destination is vacant (not bought), then the corresponding value_orig or value_dest will have a null value in the result. (Because null values are not actually stored, the value will simply be missing.)
The operation must obey the following rules (otherwise you'll see error_qty = insufficient):
- orig and dest are distinct (i.e. locations are not identical)
- orig and dest exist (i.e. the locations have been previously bought).
- the signs (+/-) of both location values remain unchanged in the move.
Scan
Scan an entire set of locations and types in one operation.
The input for the Scan operation is:
Key | Value | Description |
---|---|---|
function | grid | |
action | scan | Name of operation |
locs | list of id or hash | Space-separated list of locations and/or hashes |
types | list of id | Space-separated list of types |
zeroes | flag | Option to force the scan to return 0-values |
The Scan operation combines all the specified locs and types together, forming all possible pairs, and returns the value associated with each pair. It uses Touch for locations and Look for hashes.
Normally Scan does not return any 0 values it finds, but you can force it to do so by setting the "zeroes" flag to "1". Otherwise you can simply omit that flag altogether.
For each location L in the locs list, the output will contain this entry:
Key | Value | Description |
---|---|---|
loc/L | list of qty:id | Space-separated list of value:type pairs |
Note that if a location L is completely empty for all specified types, the "loc/L" entry will also be empty, i.e. the null string. Because the output does not list entries which have null values, you will not see an entry at all for any completely empty location.
Example (specified in "KV" key-value format):
( :function =grid :action =scan :locs =cf432b0acbaa7065a852cdda9e68f4c2 7ec5db6c103d6c0ebf2110abbd01e9ab 06d384fafd07b729f2990ffdb2786e68 :types =e420516d14dd97358c44a2be37e6a403 8610c5a529e57981f189189cd9e3158d )
When you submit that request to the API, it will come back to you looking something like this:
( :function =grid :action =scan :locs =cf432b0acbaa7065a852cdda9e68f4c2 7ec5db6c103d6c0ebf2110abbd01e9ab 06d384fafd07b729f2990ffdb2786e68 :types =e420516d14dd97358c44a2be37e6a403 8610c5a529e57981f189189cd9e3158d :loc/cf432b0acbaa7065a852cdda9e68f4c2 =99804428:8610c5a529e57981f189189cd9e3158d :loc/06d384fafd07b729f2990ffdb2786e68 =14556348000:e420516d14dd97358c44a2be37e6a403 39450540000:8610c5a529e57981f189189cd9e3158d )
From this result we can determine that:
- Location 1 has 99804428 units of type 2.
- Location 2 is completely empty, with no units of either type 1 or 2.
- Location 3 has 14556348000 units of type 1, and 39450540000 units of type 2.
NOTE:
The Scan operation will scan at most 2048 pairs. This is a hard limit which cannot be circumvented. If you specify a list of locations and types which combine to form more than 2048 pairs, Scan will return the results only for the first 2048 pairs, and set error flags in the output as follows:
Key | Value | Description |
---|---|---|
status | fail | Something went wrong. |
error | excessive | tried to scan too many pairs |
error_max | 2048 | maximum allowed scan size |