Skip to content

Commit

Permalink
[docs] Improved aggregator Move documentation (#5659)
Browse files Browse the repository at this point in the history
* [docs] Improved aggregator documentation

* [docs] Typo fixes in aggregator documentation

* [docs] addressed comments
  • Loading branch information
georgemitenkov authored Dec 4, 2022
1 parent d085a3a commit 2487961
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 212 deletions.
81 changes: 17 additions & 64 deletions aptos-move/framework/aptos-framework/doc/aggregator.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,18 @@

# Module `0x1::aggregator`

This module provides an API for aggregatable integers that allow addition,
subtraction, and reading.

Design rationale (V1)
=====================
Aggregator can be seen as a parellizable integer that supports addition,
subtraction and reading. The first version (V1) of aggregator has the
the following specification.

add(value: u128)
Speculatively adds a <code>value</code> to aggregator. This is a cheap operation
which is parallelizable. If the result of addition overflows a <code>limit</code>
(one of aggregator's fields), an error is produced and the execution
aborts.

sub(value: u128)
Speculatively subtracts a <code>value</code> from aggregator. This is a cheap
operation which is parallelizable. If the result goes below zero, an
error is produced and the execution aborts.

read(): u128
Reads (materializes) the value of an aggregator. This is an expensive
operation which usually involves reading from the storage.

destroy()
Destroys and aggregator, also cleaning up storage if necessary.

Note that there is no constructor in <code><a href="aggregator.md#0x1_aggregator_Aggregator">Aggregator</a></code> API. This is done on purpose.
For every aggregator, we need to know where its value is stored on chain.
Currently, Move does not allow fine grained access to struct fields. For
example, given a struct

struct Foo<A> has key {
a: A,
b: u128,
}

there is no way of getting a value of <code>Foo::a</code> without hardcoding the layout
of <code>Foo</code> and the field offset. To mitigate this problem, one can use a table.
Every item stored in the table is uniqely identified by (handle, key) pair:
<code>handle</code> identifies a table instance, <code>key</code> identifies an item within the table.

So how is this related to aggregator? Well, aggregator can reuse the table's
approach for fine-grained storage. However, since native functions only see a
reference to aggregator, we must ensure that both <code>handle</code> and <code>key</code> are
included as fields. Therefore, the struct looks like

struct Aggregator {
handle: u128,
key: u128,
..
}

Remaining question is how to generate this (handle, key) pair. For that, we have
a dedicated struct called <code>AggregatorFactory</code> which is responsible for constructing
aggregators. See <code><a href="aggregator_factory.md#0x1_aggregator_factory">aggregator_factory</a>.<b>move</b></code> for more details.

Advice to users (V1)
====================
Users are encouraged to use "cheap" operations (e.g. additions) to exploit the
parallelism in execution.
This module provides an interface for aggregators. Aggregators are similar to
unsigned integers and support addition and subtraction (aborting on underflow
or on overflowing a custom upper limit). The difference from integers is that
aggregators allow to perform both additions and subtractions in parallel across
multiple transactions, enabling parallel execution. For example, if the first
transaction is doing <code><a href="aggregator.md#0x1_aggregator_add">add</a>(X, 1)</code> for aggregator resource <code>X</code>, and the second
is doing <code><a href="aggregator.md#0x1_aggregator_sub">sub</a>(X,3)</code>, they can be executed in parallel avoiding a read-modify-write
dependency.
However, reading the aggregator value (i.e. calling <code><a href="aggregator.md#0x1_aggregator_read">read</a>(X)</code>) is an expensive
operation and should be avoided as much as possible because it reduces the
parallelism. Moreover, **aggregators can only be created by Aptos Framework (0x1)
at the moment.**


- [Struct `Aggregator`](#0x1_aggregator_Aggregator)
Expand All @@ -88,6 +39,8 @@ parallelism in execution.

## Struct `Aggregator`

Represents an integer which supports parallel additions and subtractions
across multiple transactions. See the module description for more details.


<pre><code><b>struct</b> <a href="aggregator.md#0x1_aggregator_Aggregator">Aggregator</a> <b>has</b> store
Expand Down Expand Up @@ -130,7 +83,7 @@ parallelism in execution.

<a name="0x1_aggregator_EAGGREGATOR_OVERFLOW"></a>

When the value of aggregator (actual or accumulated) overflows (raised by native code).
The value of aggregator overflows. Raised by native code.


<pre><code><b>const</b> <a href="aggregator.md#0x1_aggregator_EAGGREGATOR_OVERFLOW">EAGGREGATOR_OVERFLOW</a>: u64 = 1;
Expand All @@ -140,7 +93,7 @@ When the value of aggregator (actual or accumulated) overflows (raised by native

<a name="0x1_aggregator_EAGGREGATOR_UNDERFLOW"></a>

When the value of aggregator (actual or accumulated) underflows, i.e goes below zero (raised by native code).
The value of aggregator underflows (goes below zero). Raised by native code.


<pre><code><b>const</b> <a href="aggregator.md#0x1_aggregator_EAGGREGATOR_UNDERFLOW">EAGGREGATOR_UNDERFLOW</a>: u64 = 2;
Expand All @@ -150,7 +103,7 @@ When the value of aggregator (actual or accumulated) underflows, i.e goes below

<a name="0x1_aggregator_ENOT_SUPPORTED"></a>

When aggregator feature is not supported (raised by native code).
Aggregator feature is not supported. Raised by native code.


<pre><code><b>const</b> <a href="aggregator.md#0x1_aggregator_ENOT_SUPPORTED">ENOT_SUPPORTED</a>: u64 = 3;
Expand Down
43 changes: 11 additions & 32 deletions aptos-move/framework/aptos-framework/doc/aggregator_factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,11 @@

# Module `0x1::aggregator_factory`

This module provides foundations to create aggregators in the system.

Design rationale (V1)
=====================
First, we encourage the reader to see rationale of <code>Aggregator</code> in
<code><a href="aggregator.md#0x1_aggregator">aggregator</a>.<b>move</b></code>.

Recall that the value of any aggregator can be identified in storage by
(handle, key) pair. How this pair can be generated? Short answer: with
<code><a href="aggregator_factory.md#0x1_aggregator_factory_AggregatorFactory">AggregatorFactory</a></code>!

<code><a href="aggregator_factory.md#0x1_aggregator_factory_AggregatorFactory">AggregatorFactory</a></code> is a struct that can be stored as a resource on some
account and which contains a <code>phantom_table</code> field. When the factory is
initialized, we initialize this table. Importantly, table initialization
only generates a uniue table <code>handle</code> - something we can reuse.

When the user wants to create a new aggregator, he/she calls a constructor
provided by the factory (<code><a href="aggregator_factory.md#0x1_aggregator_factory_create_aggregator">create_aggregator</a>(..)</code>). This constructor generates
a unique key, which with the handle is used to initialize <code>Aggregator</code> struct.

Use cases
=========
We limit the usage of <code><a href="aggregator_factory.md#0x1_aggregator_factory_AggregatorFactory">AggregatorFactory</a></code> by only storing it on the core
account.

When something whants to use an aggregator, the factory is queried and an
aggregator instance is created. Once aggregator is no longer in use, it
should be destroyed by the user.
This module provides foundations to create aggregators. Currently only
Aptos Framework (0x1) can create them, so this module helps to wrap
the constructor of <code>Aggregator</code> struct so that only a system account
can initialize one. In the future, this might change and aggregators
can be enabled for the public.


- [Resource `AggregatorFactory`](#0x1_aggregator_factory_AggregatorFactory)
Expand All @@ -55,7 +32,9 @@ should be destroyed by the user.

## Resource `AggregatorFactory`

Struct that creates aggregators.
Creates new aggregators. Used to control the numbers of aggregators in the
system and who can create them. At the moment, only Aptos Framework (0x1)
account can.


<pre><code><b>struct</b> <a href="aggregator_factory.md#0x1_aggregator_factory_AggregatorFactory">AggregatorFactory</a> <b>has</b> key
Expand Down Expand Up @@ -86,7 +65,7 @@ Struct that creates aggregators.

<a name="0x1_aggregator_factory_EAGGREGATOR_FACTORY_NOT_FOUND"></a>

When aggregator factory is not published yet.
Aggregator factory is not published yet.


<pre><code><b>const</b> <a href="aggregator_factory.md#0x1_aggregator_factory_EAGGREGATOR_FACTORY_NOT_FOUND">EAGGREGATOR_FACTORY_NOT_FOUND</a>: u64 = 1;
Expand All @@ -98,8 +77,7 @@ When aggregator factory is not published yet.

## Function `initialize_aggregator_factory`

Can only be called during genesis.
Creates a new factory for aggregators.
Creates a new factory for aggregators. Can only be called during genesis.


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="aggregator_factory.md#0x1_aggregator_factory_initialize_aggregator_factory">initialize_aggregator_factory</a>(aptos_framework: &<a href="../../aptos-stdlib/../move-stdlib/doc/signer.md#0x1_signer">signer</a>)
Expand Down Expand Up @@ -187,6 +165,7 @@ to allow any signer to call.

## Function `new_aggregator`

Returns a new aggregator.


<pre><code><b>fun</b> <a href="aggregator_factory.md#0x1_aggregator_factory_new_aggregator">new_aggregator</a>(<a href="aggregator_factory.md#0x1_aggregator_factory">aggregator_factory</a>: &<b>mut</b> <a href="aggregator_factory.md#0x1_aggregator_factory_AggregatorFactory">aggregator_factory::AggregatorFactory</a>, limit: u128): <a href="aggregator.md#0x1_aggregator_Aggregator">aggregator::Aggregator</a>
Expand Down
23 changes: 14 additions & 9 deletions aptos-move/framework/aptos-framework/doc/optional_aggregator.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ aggregator (parallelizable) or via normal integers.

## Struct `Integer`

Wrapper around integer to have a custom overflow limit. Note that
Move has no traits (and trait bounds), so integer value must be u128.
<code><a href="optional_aggregator.md#0x1_optional_aggregator_Integer">Integer</a></code> provides API to add/subtract and read, just like <code>Aggregator</code>.
Wrapper around integer with a custom overflow limit. Supports add, subtract and read just like <code>Aggregator</code>.


<pre><code><b>struct</b> <a href="optional_aggregator.md#0x1_optional_aggregator_Integer">Integer</a> <b>has</b> store
Expand Down Expand Up @@ -79,8 +77,7 @@ Move has no traits (and trait bounds), so integer value must be u128.

## Struct `OptionalAggregator`

Struct that contains either an aggregator or a normal integer, both
overflowing on limit.
Contains either an aggregator or a normal integer, both overflowing on limit.


<pre><code><b>struct</b> <a href="optional_aggregator.md#0x1_optional_aggregator_OptionalAggregator">OptionalAggregator</a> <b>has</b> store
Expand Down Expand Up @@ -117,6 +114,7 @@ overflowing on limit.

<a name="0x1_optional_aggregator_EAGGREGATOR_OVERFLOW"></a>

The value of aggregator underflows (goes below zero). Raised by native code.


<pre><code><b>const</b> <a href="optional_aggregator.md#0x1_optional_aggregator_EAGGREGATOR_OVERFLOW">EAGGREGATOR_OVERFLOW</a>: u64 = 1;
Expand All @@ -126,6 +124,7 @@ overflowing on limit.

<a name="0x1_optional_aggregator_EAGGREGATOR_UNDERFLOW"></a>

Aggregator feature is not supported. Raised by native code.


<pre><code><b>const</b> <a href="optional_aggregator.md#0x1_optional_aggregator_EAGGREGATOR_UNDERFLOW">EAGGREGATOR_UNDERFLOW</a>: u64 = 2;
Expand All @@ -137,6 +136,7 @@ overflowing on limit.

## Function `new_integer`

Creates a new integer which overflows on exceeding a <code>limit</code>.


<pre><code><b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_new_integer">new_integer</a>(limit: u128): <a href="optional_aggregator.md#0x1_optional_aggregator_Integer">optional_aggregator::Integer</a>
Expand Down Expand Up @@ -164,6 +164,7 @@ overflowing on limit.

## Function `add_integer`

Adds <code>value</code> to integer. Aborts on overflowing the limit.


<pre><code><b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_add_integer">add_integer</a>(integer: &<b>mut</b> <a href="optional_aggregator.md#0x1_optional_aggregator_Integer">optional_aggregator::Integer</a>, value: u128)
Expand Down Expand Up @@ -192,6 +193,7 @@ overflowing on limit.

## Function `sub_integer`

Subtracts <code>value</code> from integer. Aborts on going below zero.


<pre><code><b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_sub_integer">sub_integer</a>(integer: &<b>mut</b> <a href="optional_aggregator.md#0x1_optional_aggregator_Integer">optional_aggregator::Integer</a>, value: u128)
Expand All @@ -217,6 +219,7 @@ overflowing on limit.

## Function `limit`

Returns an overflow limit of integer.


<pre><code><b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_limit">limit</a>(integer: &<a href="optional_aggregator.md#0x1_optional_aggregator_Integer">optional_aggregator::Integer</a>): u128
Expand All @@ -241,6 +244,7 @@ overflowing on limit.

## Function `read_integer`

Returns a value stored in this integer.


<pre><code><b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_read_integer">read_integer</a>(integer: &<a href="optional_aggregator.md#0x1_optional_aggregator_Integer">optional_aggregator::Integer</a>): u128
Expand All @@ -265,6 +269,7 @@ overflowing on limit.

## Function `destroy_integer`

Destroys an integer.


<pre><code><b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_destroy_integer">destroy_integer</a>(integer: <a href="optional_aggregator.md#0x1_optional_aggregator_Integer">optional_aggregator::Integer</a>)
Expand All @@ -289,7 +294,7 @@ overflowing on limit.

## Function `new`

Creates a new optional aggregator instance.
Creates a new optional aggregator.


<pre><code><b>public</b>(<b>friend</b>) <b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_new">new</a>(limit: u128, parallelizable: bool): <a href="optional_aggregator.md#0x1_optional_aggregator_OptionalAggregator">optional_aggregator::OptionalAggregator</a>
Expand Down Expand Up @@ -534,7 +539,7 @@ Destroys non-parallelizable optional aggregator and returns its limit.

## Function `add`

Adds to optional aggregator, aborting on exceeding the <code>limit</code>.
Adds <code>value</code> to optional aggregator, aborting on exceeding the <code>limit</code>.


<pre><code><b>public</b> <b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_add">add</a>(<a href="optional_aggregator.md#0x1_optional_aggregator">optional_aggregator</a>: &<b>mut</b> <a href="optional_aggregator.md#0x1_optional_aggregator_OptionalAggregator">optional_aggregator::OptionalAggregator</a>, value: u128)
Expand Down Expand Up @@ -565,7 +570,7 @@ Adds to optional aggregator, aborting on exceeding the <code>limit</code>.

## Function `sub`

Subtracts from optional aggregator, aborting on going below zero.
Subtracts <code>value</code> from optional aggregator, aborting on going below zero.


<pre><code><b>public</b> <b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_sub">sub</a>(<a href="optional_aggregator.md#0x1_optional_aggregator">optional_aggregator</a>: &<b>mut</b> <a href="optional_aggregator.md#0x1_optional_aggregator_OptionalAggregator">optional_aggregator::OptionalAggregator</a>, value: u128)
Expand Down Expand Up @@ -627,7 +632,7 @@ Returns the value stored in optional aggregator.

## Function `is_parallelizable`

Returns true is optional aggregator uses parallelizable implementation.
Returns true if optional aggregator uses parallelizable implementation.


<pre><code><b>public</b> <b>fun</b> <a href="optional_aggregator.md#0x1_optional_aggregator_is_parallelizable">is_parallelizable</a>(<a href="optional_aggregator.md#0x1_optional_aggregator">optional_aggregator</a>: &<a href="optional_aggregator.md#0x1_optional_aggregator_OptionalAggregator">optional_aggregator::OptionalAggregator</a>): bool
Expand Down
47 changes: 47 additions & 0 deletions aptos-move/framework/aptos-framework/sources/aggregator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Design Rationale

## Aggregators

Aggregator is a parellizable integer that supports addition and subtraction.
Unlike integer, aggregator has a user-defined `limit` which specifies when
the value of aggregator overflows. Similarly to unsigned integers, the value
of an aggregator underflows when going below zero.

Both additions and subtractions are executed speculatively, and thus can be
easily parallelized. On underflow or overflow, these operations are guaranteed
to abort. Using these operations is encouraged.

Reading an aggregator's value "materializes" the speculative value, and it
can involve reading from the storage or checking for underflow or overflow.
In general, using this operation is discouraged, or at least it should be used
as rarely as possible.

## Aggregator factory

Unfortunately, aggregators cannot be part of a resource. At the moment, Move
does not allow fine-grained access to resource fields, which ruins perfromance
benefits aggregators can provide. In addition, getting the value of the field of
a resource from storage is not possible without hardcoding the struct layout.
For example, given a struct

```move
struct Foo<A> has key {
a: A,
b: u128,
}
```

there is no clean way of getting the value of `Foo::a` without knowing that the
offset is 0.

To mitigate the problem, we store aggregators as table items. Recall that every
item stored in the table is uniquely identified by `(handle, key)` pair: `handle`
identifies a table instance, and `key` identifies an item within the table. Now,
if aggregator is a table item, it can be easily queried from storage and has a
fine-grained access.

To create an aggregator, one can use an `AggregatorFactory`. It is a resource
which contains a single `phantom_table` field. When the factory is initialized,
this field is used to generate a unique table `handle` which is passed to all
new aggregators. When a new aggregator instance is created, it has a unique
`key` which together with the `handle` is stored in `Aggregator` struct.
Loading

0 comments on commit 2487961

Please sign in to comment.