Жизненный цикл транзакции
Tags: aptos, blockchain, tutorial, developer
Published: 2022-09-01 16:20:29

Чтобы глубже понять жизненный цикл транзакции Aptos (с операционной точки зрения), мы проследим путь транзакции от подачи на полную ноду Aptos до фиксации в блокчейне Aptos. Затем мы увеличим масштаб логических компонентов нод Aptos и рассмотрим, как транзакция взаимодействует с этими компонентами.

## Предположения
Для целей данного документа мы будем считать, что:

- Alice и Bob - два пользователя, у каждого из которых есть учетная запись на блокчейне Aptos.
- В учетной записи Alice есть 110 монет Aptos Coins.
- Алиса отправляет Bob 10  Aptos Coins .
- Текущий [порядковый номер](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Sequence%20Number%20%2D%20%D0%9F%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%BE%D0%BC%D0%B5%D1%80) учетной записи Alice - 5 (это означает, что с учетной записи Alice уже было отправлено 5 транзакций).
- Всего в сети 100 нод-валидаторов - от V1 до V100.
- Клиент Aptos отправляет транзакцию Alice в REST-сервис на полную ноду Aptos. Полная нода пересылает эту транзакцию валидатору полной ноды, который, в свою очередь, пересылает ее валидатору V1.
- Валидатор V1 является инициатором/лидером текущего раунда.


## Пользователь отправляет транзакцию
**Пользователь Aptos создает необработанную транзакцию** (назовем ее Traw5) для перевода 10 Aptos Coins с учетной записи Alice на учетную запись Bob. Пользователь Aptos подписывает транзакцию закрытым ключом Alice. Подписанная транзакция T5 включает в себя следующее:

- Необработанная транзакция.
- Открытый ключ Alice.
- Подпись Alice.

Необработанная транзакция включает следующие поля:

[Адрес учетной записи](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Account%20%2D%20%D0%A3%D1%87%D0%B5%D1%82%D0%BD%D0%B0%D1%8F%20%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D1%8C) - Адрес учетной записи Alice
Модуль Move - Модуль (или программа), который указывает действия, которые должны быть выполнены от имени Alice. В данном случае он содержит:
- Скрипт одноранговой [транзакции](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Transaction%20%2D%20%D0%A2%D1%80%D0%B0%D0%BD%D0%B7%D0%B0%D0%BA%D1%86%D0%B8%D1%8F) Move bytecode
- Список входных данных для скрипта (в данном примере список содержит адрес учетной записи Bob'а и сумму платежа в Aptos Coins).

[Максимальное количество газа](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Maximum%20Gas%20Amount%20%2D%20%D0%9C%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F%20%D1%81%D1%83%D0%BC%D0%BC%D0%B0%20%D0%B3%D0%B0%D0%B7%D0%B0%20%D0%B4%D0%BB%D1%8F%20%D1%82%D1%80%D0%B0%D0%BD%D0%B7%D0%B0%D0%BA%D1%86%D0%B8%D0%B8) - Максимальное количество газа, которое Alice готова заплатить за эту транзакцию. Газ - это способ оплаты вычислений и хранения. Единица газа - это абстрактное измерение вычислений.

[Цена газа](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Gas%20Price%20%2D%20%D0%A6%D0%B5%D0%BD%D0%B0%20%D0%B3%D0%B0%D0%B7%D0%B0) - Сумма (в Aptos Coins), которую Alice готова заплатить за единицу газа, чтобы выполнить транзакцию.

[Срок действия](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Expiration%20Time%20%2D%20%D0%A1%D1%80%D0%BE%D0%BA%20%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D1%8F) - Время истечения срока действия транзакции.

[Порядковый номер](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Sequence%20Number%20%2D%20%D0%9F%D0%BE%D1%80%D1%8F%D0%B4%D0%BA%D0%BE%D0%B2%D1%8B%D0%B9%20%D0%BD%D0%BE%D0%BC%D0%B5%D1%80) - Порядковый номер (в данном примере - 5) для учетной записи указывает на количество транзакций, которые были отправлены и зафиксированы в сети с этой учетной записи. В данном случае с учетной записи Alice было отправлено 5 транзакций, включая Traw5. Примечание: транзакция с порядковым номером 5 может быть зафиксирована в сети, только если порядковый номер учетной записи равен 5.

[Chain ID](https://github.com/aptos-labs/aptos-core/blob/main/types/src/chain_id.rs) - Идентификатор, который отличает развертывания сети Aptos (для предотвращения межсетевых атак).

## Жизненный цикл транзакции

В этом разделе мы опишем жизненный цикл транзакции T5, начиная с момента ее отправки пользователем и заканчивая фиксацией в блокчейне Aptos.

Для соответствующих этапов мы включили ссылку на соответствующие межкомпонентные взаимодействия в ноде валидатора. После того как вы ознакомитесь со всеми этапами жизненного цикла транзакции, вы можете обратиться к информации о соответствующих межкомпонентных взаимодействиях для каждого этапа.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5b6b711bct4vsybpyz4s.jpg)
Рисунок 1.0 Жизненный цикл транзакции

> _Стрелки на всех иллюстрациях в этой статье начинаются от компонента, инициирующего взаимодействие/действие, и заканчиваются на компоненте, над которым выполняется действие. Стрелки не представляют данные, прочитанные, записанные или возвращенные._

Жизненный цикл транзакции состоит из пяти этапов:

- Принятие: Принятие транзакции
- Совместное использование: Совместное использование транзакции с другими нодами валидатора
- Предложение: Предложение блока
- Выполнение и консенсус: Выполнение блока и достижение консенсуса
- Завершение: завершение блока

Ниже мы описали, что происходит на каждом этапе, а также ссылки на соответствующие взаимодействия компонентов ноды Aptos.

## Принятие транзакции
1. **REST-сервис - Пользователь → REST-сервис**: Пользователь отправляет транзакцию T5 в REST-сервис полной ноды Aptos . Полная нода использует REST-сервис для пересылки транзакции в свой собственный mempool, который затем пересылает транзакцию в mempool, запущенные на других нодах сети. В конечном итоге транзакция будет направлена в mempool, работающий на полной ноде валидаторном , который отправит ее на валидаторную ноду (в данном случае V1).
2. **REST-сервис → Mempool**: REST-сервис полной ноды передает транзакцию T5 в mempool валидатора V1.
3. **Mempool → Виртуальная машина**: Mempool будет использовать компонент виртуальной машины для выполнения проверки транзакций, таких как проверка подписи, проверка баланса учетной записи и защита от повторного воспроизведения с помощью порядкового номера.

## Совместное использование транзакции с другими нодами валидатора

4. **Mempool**: Mempool будет хранить T5 в буфере в памяти. Mempool может уже содержать несколько транзакций, отправленных с адреса Alice.
5. **Mempool → другие валидаторы**: Используя протокол shared-mempool, V1 будет делиться транзакциями (включая T5) в своем mempool с другими нодами-валидаторами и помещать транзакции, полученные от них, в свой собственный (V1) mempool.

## Предложение блока
6. **Консенсус → Mempool**: - Поскольку валидатор V1 является инициатором/лидером данной транзакции, он извлекает блок транзакций из своего mempool и реплицирует этот блок в качестве предложения другим нодами валидатора через свой компонент консенсуса.
7. **Консенсус → остальные валидаторы**: Компонент консенсуса V1 отвечает за координацию согласия между всеми валидаторами относительно порядка транзакций в предлагаемом блоке.

## Выполнение блока и достижение консенсуса

8. **Консенсус → Выполнение**: В рамках достижения соглашения блок транзакций (содержащий T5) передается компоненту выполнения.
9. **Выполнение → Виртуальная машина**: Компонент исполнения управляет выполнением транзакций в виртуальной машине. Обратите внимание, что это выполнение происходит спекулятивно до того, как транзакции в блоке были согласованы.
10. **Консенсус → Выполнение**: После выполнения транзакций в блоке компонент выполнения добавляет транзакции в блоке (включая T5) в аккумулятор Merkle (истории реестра). Это встроенная в память/временная версия аккумулятора Merkle. Необходимая часть предполагаемого/спекулятивного результата выполнения этих транзакций возвращается в компонент консенсуса для согласования. Стрелка от "консенсуса" к " выполнению" указывает на то, что запрос на выполнение транзакций был сделан компонентом консенсуса.
11. **Консенсус → остальные валидаторы**: V1 (лидер консенсуса) пытается достичь консенсуса по результату выполнения предложенного блока с другими нодами-валидаторами, участвующими в консенсусе.

## Завершение блока
12. **Консенсус → Выполнение, Выполнение → Хранилище**: Если результат выполнения предложенного блока согласован и подписан набором валидаторов, имеющих кворум голосов, компонент выполнения валидатора V1 считывает полный результат выполнения предложенного блока из кэша спекулятивного выполнения и фиксирует все транзакции в предложенном блоке в постоянном хранилище с их результатами.

На учетной записи Alice теперь будет 100 Aptos Coins, а ее порядковый номер будет равен 6. Если Bob воспроизведет транзакцию T5, она будет отклонена, поскольку порядковый номер учетной записи Alice (6) больше, чем порядковый номер воспроизведенной транзакции (5).

## Взаимодействие компонентов ноды Aptos

В предыдущем разделе мы описали типичный жизненный цикл транзакции (от отправки транзакции до ее завершения). Теперь давайте рассмотрим межкомпонентное взаимодействие нод Aptos, когда блокчейн обрабатывает транзакции и отвечает на запросы. Эта информация будет наиболее полезна тем, кто:

- Хотели бы получить представление о том, как работает данная система под прикрытием.
- Заинтересованы в том, чтобы в конечном итоге внести свой вклад в блокчейн Aptos.

Вы можете узнать больше о различных типах нод Aptos здесь:

- [Ноды валидатора](https://dev.to/egormajj/kontsieptsii-aptos-nody-validatora-2p1d)
- [Полные ноды](https://dev.to/egormajj/kontsieptsii-aptos-polnyie-nody-33e)

Для нашего описания мы будем считать, что пользователь отправляет транзакцию TN валидатору VX. Для каждого компонента валидатора мы опишем каждое из его межкомпонентных взаимодействий в подразделах раздела соответствующего компонента. Обратите внимание, что подразделы, описывающие межкомпонентные взаимодействия, перечислены не строго в том порядке, в котором они выполняются. Большинство взаимодействий имеют отношение к обработке транзакции, а некоторые - к запросам пользователей к блокчейну (запросы на существующую информацию в блокчейне).

Ниже перечислены основные компоненты ноды Aptos, используемые в жизненном цикле транзакции:

**Полная нода**

- REST-сервис

**Нода валидатора**

- Mempool
- Консенсус
- Выполнение
- Виртуальная машина
- Хранилище

## REST-сервис

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r1zyynmpwwytyztsizi8.jpg)

_Рисунок 1.1 Сервис REST_
Любой запрос, сделанный клиентом, сначала поступает в REST-сервис полной ноды. Затем отправленная транзакция передается валидатору Полной ноды, который затем отправляет ее валидатору ноды VX.

## 1. Пользователь → REST-сервис
Пользователь отправляет транзакцию в REST-сервис полного ноды Aptos.

## 2. REST-сервис → Mempool
Служба REST передает транзакцию валидатору полной ноды, который затем отправляет ее в mempool валидатора ноды VX. Mempool примет транзакцию TN, только если порядковый номер TN больше или равен текущему порядковому номеру учетной записи отправителя (обратите внимание, что транзакция не будет передана на консенсус, пока порядковый номер не совпадет с порядковым номером учетной записи отправителя).

## 3. REST-сервис → хранилище
Когда пользователь выполняет запрос на чтение блокчейна Aptos (например, чтобы получить баланс учетной записи Alice), REST-сервис напрямую взаимодействует с компонентом хранилища для получения запрашиваемой информации.

## Виртуальная машина

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a1fywh2ihlz32g5s25l4.jpg)
_Рисунок 1.2 Виртуальная машина_
Виртуальная машина Move проверяет и выполняет скрипты транзакций, написанные в байткоде Move.

## 1. Виртуальная машина → Хранилище

Когда mempool запрашивает виртуальную машину для проверки транзакции через `VM::ValidateTransaction()`, виртуальная машина загружает учетную запись отправителя транзакции из хранилища и выполняет проверки, некоторые из которых описаны в списке ниже. Посмотреть весь список проверок можно здесь.

- Проверяет правильность входной подписи в подписанной транзакции (для отбраковки неправильно подписанных транзакций).
- Проверяет, что ключ аутентификации учетной записи отправителя совпадает с хэшем открытого ключа (соответствующего закрытому ключу, используемому для подписания транзакции).
- Проверяет, что номер последовательности для транзакции больше или равен текущему номеру последовательности для учетной записи отправителя. Выполнение этой проверки предотвращает повторение той же транзакции по учетной записи отправителя.
- Проверяет, что программа в подписанной транзакции не является искаженной, так как искаженная программа не может быть выполнена виртуальной машиной.
- Проверяет, что баланс учетной записи отправителя содержит как минимум максимальное количество газа, умноженное на цену газа, указанную в транзакции, что гарантирует, что транзакция может оплатить ресурсы, которые она использует.

## 2. Выполнение → Виртуальная машина

Компонент выполнения использует виртуальную машину для выполнения транзакции через `VM::ExecuteTransaction()`.

Важно понимать, что выполнение транзакции отличается от обновления состояния реестра и сохранения результатов в хранилище. Транзакция TN сначала выполняется как часть попытки достичь согласия по блокам во время консенсуса. Если с другими валидаторами достигнуто соглашение об упорядочении транзакций и результатах их выполнения, результаты сохраняются в хранилище, а состояние реестра обновляется.

## 3. Mempool → Виртуальная машина

Когда mempool получает транзакцию от других валидаторов через общий mempool или от службы REST, mempool вызывает `VM::ValidateTransaction()` на виртуальной машине для проверки транзакции.

Подробности реализации см. в [README виртуальной машины.](https://github.com/move-language/move/tree/main/language/move-vm)

## Mempool


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrvpwz2rm28q1hwsc81a.jpg)
_Рисунок 1.3 Mempool_
Mempool - это общий буфер, в котором хранятся транзакции, "ожидающие" выполнения. Когда новая транзакция добавляется в mempool, mempool делится этой транзакцией с другими нодами-валидаторами в системе. Чтобы снизить потребление сети в "общем Mempool", каждый валидатор отвечает за отправку собственных транзакций другим валидаторам. Когда валидатор получает транзакцию из Mempool другого валидатора, транзакция добавляется в Mempool валидатора-получателя.

## 1. REST-сервис → Mempool

- После получения транзакции от пользователя сервис REST отправляет транзакцию на полную ноду валидатора . Затем транзакция отправляется в mempool ноды-валидатора.
- Mempool валидаторной ноды VX принимает транзакцию TN для учетной записи отправителя только в том случае, если порядковый номер TN больше или равен текущему порядковому номеру учетной записи отправителя.

## 2. Mempool → Другие ноды валидатора

- Mempool ноды валидатора VX разделяет транзакцию TN с другими валидаторами в той же сети.
- Другие валидаторы делят транзакции в своих соответствующих mempool с пулом mempool ноды VX.

## 3. Консенсус → Mempool

Когда транзакция направляется на ноду-валидатора и когда нода-валидатора становится лидером, его компонент консенсуса извлекает блок транзакций из своего mempool и воспроизводит предложенный блок другим валидаторам. Это делается для того, чтобы прийти к консенсусу относительно порядка транзакций и результатов выполнения транзакций в предложенном блоке.
Обратите внимание, что если транзакция TN была включена в предложенный блок консенсуса, это не гарантирует, что TN в конечном итоге будет сохранена в распределенной базе данных блокчейна Aptos.

## 4. Mempool → Виртуальная машина

Когда mempool получает транзакцию от других валидаторов, mempool вызывает `VM::ValidateTransaction()` на виртуальной машине для проверки транзакции.

Подробности реализации см. в [Mempool README](https://github.com/aptos-labs/aptos-core/tree/main/documentation/specifications/mempool).

## Консенсус


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxjpwzgkwsqa19z6bq0l.jpg)
_Рисунок 1.4 Консенсус_
Компонент консенсуса отвечает за упорядочивание блоков транзакций и согласование результатов выполнения путем участия в протоколе консенсуса с другими валидаторами в сети.

## 1. Консенсус → Mempool
Когда валидатор VX является лидером/инициатором, компонент консенсуса VX извлекает блок транзакций из своего mempool через: `Mempool::GetBlock()`, и формирует предлагаемый блок транзакций.

## 2. Консенсус → остальные валидаторы
Если VX является инициатором/лидером, его компонент консенсуса воспроизводит предложенный блок транзакций другим валидаторам.

3. Консенсус → выполнение, консенсус → остальные валидаторы
Чтобы выполнить блок транзакций, консенсус взаимодействует с компонентом выполнения. Консенсус выполняет блок транзакций через `Execution:ExecuteBlock()` (См. Консенсус → выполнение).
После выполнения транзакций в предложенном блоке компонент выполнения отвечает компоненту консенсуса результатом выполнения этих транзакций.
Компонент консенсуса подписывает результат выполнения и пытается достичь согласия по этому результату с другими валидаторами.

## 4. Консенсус → Выполнение

Если достаточное количество валидаторов голосует за одинаковый результат выполнения, компонент консенсуса VX информирует выполнение через `Execution::CommitBlock()`, что этот блок готов к завершению.

Подробности реализации см. в [Consensus README](https://github.com/aptos-labs/aptos-core/tree/main/documentation/specifications/consensus).

## Выполнение

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/blx66ago6qzbcf05fttk.jpg)
_Рисунок 1.5 Выполнение_
Компонент выполнения координирует выполнение блока транзакций и поддерживает переходное состояние, по которому может быть проведено консенсусное голосование. Если эти транзакции успешны, они фиксируются в хранилище.

## 1. Консенсус → Выполнение
Консенсус запрашивает выполнение для выполнения блока транзакций через: `Execution::ExecuteBlock()`.
Выполнение поддерживает "блокнот", который хранит в памяти копии соответствующих частей [аккумулятора Merkle](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Accumulator%20Root%20Hash%20%2D%20%D0%9A%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%85%D1%8D%D1%88%20%D0%B0%D0%BA%D0%BA%D1%83%D0%BC%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80%D0%B0%20%2D%20%D1%8D%D1%82%D0%BE%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%85%D1%8D%D1%88%20%D0%B0%D0%BA%D0%BA%D1%83%D0%BC%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80%D0%B0%20Merkle.). Эта информация используется для вычисления корневого хэша текущего состояния блокчейна Aptos.
Корневой хэш текущего состояния объединяется с информацией о транзакциях в предлагаемом блоке для определения нового корневого хэша аккумулятора. Это делается до сохранения каких-либо данных, а также для того, чтобы гарантировать, что ни одно состояние или транзакция не будут сохранены до тех пор, пока не будет достигнуто согласие кворума валидаторов.
В ходе выполнения вычисляется спекулятивный корневой хэш, затем компонент консенсуса VX подписывает этот корневой хэш и пытается достичь согласия по этому корневому хэшу с другими валидаторами.

## 2. Выполнение → виртуальная машина
Когда консенсус запрашивает выполнение на выполнение блока транзакций через `Execution::ExecuteBlock()`, выполнение использует виртуальную машину для определения результатов выполнения блока транзакций.

## 3. Консенсус → Выполнение
Если кворум валидаторов согласен с результатами выполнения блока, компонент консенсуса каждого валидатора сообщает своему компоненту выполнения через `Execution::CommitBlock()`, что этот блок готов к фиксации. Этот вызов компонента исполнения будет включать подписи валидаторов, чтобы обеспечить доказательство их согласия.

## 4. Выполнение → Хранилище
Выполнение берет значения из своей "скретч-панели" и отправляет их в хранилище для сохранения через `Storage::SaveTransactions()`. Затем выполнение удаляет из "скрэтчпада" старые значения, которые больше не нужны (например, параллельные блоки, которые не могут быть завершены).

Подробности реализации см. в [README Execution](https://github.com/aptos-labs/aptos-core/tree/main/execution).

## Хранилище

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rp5mvt2c6lkxz8wsgr2.jpg)
_Рисунок 1.6 Хранилище_
Компонент хранилища сохраняет согласованные блоки транзакций и результаты их выполнения в блокчейне Aptos. Блок транзакций (который включает в себя транзакцию TN) будет сохранен через хранилище, когда есть согласие между более чем кворумом (2f+1) валидаторов, участвующих в консенсусе. Соглашение должно включать в себя все следующее:

- Транзакции, которые должны быть включены в блок
- Порядок транзакций
- Результаты выполнения транзакций в блоке
См. раздел "[Аккумулятор Меркла](https://dev.to/egormajj/slovar-aptos-eek#:~:text=Accumulator%20Root%20Hash%20%2D%20%D0%9A%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%85%D1%8D%D1%88%20%D0%B0%D0%BA%D0%BA%D1%83%D0%BC%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80%D0%B0%20%2D%20%D1%8D%D1%82%D0%BE%20%D0%BA%D0%BE%D1%80%D0%BD%D0%B5%D0%B2%D0%BE%D0%B9%20%D1%85%D1%8D%D1%88%20%D0%B0%D0%BA%D0%BA%D1%83%D0%BC%D1%83%D0%BB%D1%8F%D1%82%D0%BE%D1%80%D0%B0%20Merkle.)" для получения информации о том, как транзакция добавляется в структуру данных, представляющую блокчейн Aptos.

## 1. Виртуальная машина → хранилище
Когда mempool вызывает `VM::ValidateTransaction()` для проверки транзакции, `VM::ValidateTransaction()` загружает учетную запись отправителя из хранилища и выполняет проверку достоверности транзакции только для чтения.

## 2. Выполнение → хранилище
Когда компонент консенсуса вызывает `Execution::ExecuteBlock()`, выполнение считывает текущее состояние из хранилища в сочетании с данными "скрэтчпада" в памяти для определения результатов выполнения.

## 3. Выполнение → хранилище
После достижения консенсуса по блоку транзакций выполнение вызывает хранилище через `Storage::SaveTransactions()` для сохранения блока транзакций и их постоянной записи. При этом также сохраняются подписи нод-валидаторов, которые согласовали этот блок транзакций. Данные в памяти "scratchpad" для этого блока передаются для обновления хранилища и сохранения транзакций. Когда хранилище обновляется, у каждой учетной записи, которая была изменена этими транзакциями, ее порядковый номер увеличивается на единицу.

Примечание: Порядковый номер учетной записи в блокчейне Aptos увеличивается на единицу для каждой совершенной транзакции, исходящей от этой учетной записи.

## 4. REST-сервис → хранилище
Для пользовательских запросов, считывающих информацию из блокчейна, REST-сервис напрямую взаимодействует с хранилищем для считывания запрашиваемой информации.

Подробности реализации см. в [Storage README](https://github.com/aptos-labs/aptos-core/tree/main/storage).