Skip to content

Commit

Permalink
Add object code deployment methods to PackagePublisher
Browse files Browse the repository at this point in the history
- Implement object code deployment and upgrade features
- Prepare for integration with new large_packages module
- Prerequisite for updating large package publisher
  • Loading branch information
0xjunha committed Oct 2, 2024
1 parent e335bce commit fbd91ea
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 11 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ examples_cli:
poetry run python -m examples.hello_blockchain
# poetry run python -m examples.large_package_publisher CURRENTLY BROKEN -- OUT OF GAS
poetry run python -m examples.multisig
poetry run python -m examples.object_code_deployment
poetry run python -m examples.your_coin

integration_test:
Expand Down
126 changes: 115 additions & 11 deletions aptos_sdk/package_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# SPDX-License-Identifier: Apache-2.0

import os
from typing import List
from enum import Enum
from typing import List, Optional

import tomli

Expand All @@ -20,6 +21,15 @@
"0xfa3911d7715238b2e3bd5b26b6a35e11ffa16cff318bc11471e84eccee8bd291"
)

# Domain separator for the code object address derivation
OBJECT_CODE_DEPLOYMENT_DOMAIN_SEPARATOR = b"aptos_framework::object_code_deployment"


class PublishMode(Enum):
ACCOUNT_DEPLOY = "ACCOUNT_DEPLOY"
OBJECT_DEPLOY = "OBJECT_DEPLOY"
OBJECT_UPGRADE = "OBJECT_UPGRADE"


class PackagePublisher:
"""A wrapper around publishing packages."""
Expand Down Expand Up @@ -51,11 +61,62 @@ async def publish_package(
)
return await self.client.submit_bcs_transaction(signed_transaction)

async def publish_package_to_object(
self, sender: Account, package_metadata: bytes, modules: List[bytes]
) -> str:
transaction_arguments = [
TransactionArgument(package_metadata, Serializer.to_bytes),
TransactionArgument(
modules, Serializer.sequence_serializer(Serializer.to_bytes)
),
]

payload = EntryFunction.natural(
"0x1::object_code_deployment",
"publish",
[],
transaction_arguments,
)

signed_transaction = await self.client.create_bcs_signed_transaction(
sender, TransactionPayload(payload)
)
return await self.client.submit_bcs_transaction(signed_transaction)

async def upgrade_package_object(
self,
sender: Account,
package_metadata: bytes,
modules: List[bytes],
object_address: AccountAddress,
) -> str:
transaction_arguments = [
TransactionArgument(package_metadata, Serializer.to_bytes),
TransactionArgument(
modules, Serializer.sequence_serializer(Serializer.to_bytes)
),
TransactionArgument(object_address, Serializer.struct),
]

payload = EntryFunction.natural(
"0x1::object_code_deployment",
"upgrade",
[],
transaction_arguments,
)

signed_transaction = await self.client.create_bcs_signed_transaction(
sender, TransactionPayload(payload)
)
return await self.client.submit_bcs_transaction(signed_transaction)

async def publish_package_in_path(
self,
sender: Account,
package_dir: str,
large_package_address: AccountAddress = MODULE_ADDRESS,
publish_mode: PublishMode = PublishMode.ACCOUNT_DEPLOY,
code_object: Optional[AccountAddress] = None,
) -> List[str]:
with open(os.path.join(package_dir, "Move.toml"), "rb") as f:
data = tomli.load(f)
Expand All @@ -76,16 +137,55 @@ async def publish_package_in_path(
metadata_path = os.path.join(package_build_dir, "package-metadata.bcs")
with open(metadata_path, "rb") as f:
metadata = f.read()
return await self.publish_package_experimental(
sender, metadata, modules, large_package_address

# If the package size is larger than a single transaction limit, use chunked publish.
if self.is_large_package(metadata, modules):
return await self.chunked_package_publish(
sender, metadata, modules, large_package_address, publish_mode
)

# If the deployment can fit into a single transaction, use the normal package publisher
if publish_mode == PublishMode.ACCOUNT_DEPLOY:
txn_hash = await self.publish_package(sender, metadata, modules)
elif publish_mode == PublishMode.OBJECT_DEPLOY:
txn_hash = await self.publish_package_to_object(sender, metadata, modules)
elif publish_mode == PublishMode.OBJECT_UPGRADE:
if code_object is None:
raise ValueError("code_object must be provided for OBJECT_UPGRADE mode")
txn_hash = await self.upgrade_package_object(
sender, metadata, modules, code_object
)
else:
raise ValueError(f"Unexpected publish mode: {publish_mode}")

return [txn_hash]

async def derive_object_address(
self, publisher_address: AccountAddress
) -> AccountAddress:
sequence_number = await self.client.account_sequence_number(publisher_address)
return self.create_object_deployment_address(
publisher_address, sequence_number + 1
)

async def publish_package_experimental(
@staticmethod
def create_object_deployment_address(
creator_address: AccountAddress, creator_sequence_number: int
) -> AccountAddress:
ser = Serializer()
ser.to_bytes(OBJECT_CODE_DEPLOYMENT_DOMAIN_SEPARATOR)
ser.u64(creator_sequence_number)
seed = ser.output()

return AccountAddress.for_named_object(creator_address, seed)

async def chunked_package_publish(
self,
sender: Account,
package_metadata: bytes,
modules: List[bytes],
large_package_address: AccountAddress = MODULE_ADDRESS,
publish_mode: PublishMode = PublishMode.ACCOUNT_DEPLOY,
) -> List[str]:
"""
Chunks the package_metadata and modules across as many transactions as necessary.
Expand All @@ -94,13 +194,6 @@ async def publish_package_experimental(
optimistic transaction batching. The batching tries to place as much data in a transaction
before moving to the chunk to the next transaction.
"""
# If this can fit into a single transaction, use the normal package publisher
total_size = len(package_metadata)
for module in modules:
total_size += len(module)
if total_size < MAX_TRANSACTION_SIZE:
txn_hash = await self.publish_package(sender, package_metadata, modules)
return [txn_hash]

# Chunk the metadata and insert it into payloads. The last chunk may be small enough
# to be placed with other data. This may also be the only chunk.
Expand Down Expand Up @@ -194,6 +287,17 @@ def create_large_package_publishing_payload(

return TransactionPayload(payload)

@staticmethod
def is_large_package(
package_metadata: bytes,
modules: List[bytes],
) -> bool:
total_size = len(package_metadata)
for module in modules:
total_size += len(module)

return total_size >= MAX_TRANSACTION_SIZE

@staticmethod
def create_chunks(data: bytes) -> List[bytes]:
chunks: List[bytes] = []
Expand Down
79 changes: 79 additions & 0 deletions examples/object_code_deployment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright © Aptos Foundation
# SPDX-License-Identifier: Apache-2.0

import asyncio
import os
import sys

from aptos_sdk.account import Account
from aptos_sdk.aptos_cli_wrapper import AptosCLIWrapper
from aptos_sdk.async_client import FaucetClient, RestClient
from aptos_sdk.package_publisher import MODULE_ADDRESS, PackagePublisher, PublishMode

from .common import APTOS_CORE_PATH, FAUCET_URL, NODE_URL


async def main(package_dir):
rest_client = RestClient(NODE_URL)
faucet_client = FaucetClient(FAUCET_URL, rest_client)
package_publisher = PackagePublisher(rest_client)
alice = Account.generate()

print("\n=== Publisher Address ===")
print(f"Alice: {alice.address()}")

await faucet_client.fund_account(alice.address(), 100_000_000)

print("\n=== Initial Coin Balance ===")
alice_balance = await rest_client.account_balance(alice.address())
print(f"Alice: {alice_balance}")

# The object address is derived from publisher's address and sequence number.
code_object_address = await package_publisher.derive_object_address(alice.address())
module_name = "hello_blockchain"

print("\nCompiling package...")
if AptosCLIWrapper.does_cli_exist():
AptosCLIWrapper.compile_package(package_dir, {module_name: code_object_address})
else:
print(f"Address of the object to be created: {code_object_address}")
input(
"\nUpdate the module with the derived code object address, compile, and press enter."
)

# Deploy package to code object.
print("\n=== Object Code Deployment ===")
deploy_txn_hash = await package_publisher.publish_package_in_path(
alice, package_dir, MODULE_ADDRESS, publish_mode=PublishMode.OBJECT_DEPLOY
)

print(f"Tx submitted: {deploy_txn_hash[0]}")
await rest_client.wait_for_transaction(deploy_txn_hash[0])
print(f"Package deployed to object {code_object_address}")

print("\n=== Object Code Upgrade ===")
upgrade_txn_hash = await package_publisher.publish_package_in_path(
alice,
package_dir,
MODULE_ADDRESS,
publish_mode=PublishMode.OBJECT_UPGRADE,
code_object=code_object_address,
)
print(f"Tx submitted: {upgrade_txn_hash[0]}")
await rest_client.wait_for_transaction(upgrade_txn_hash[0])
print(f"Package in object {code_object_address} upgraded")
await rest_client.close()


if __name__ == "__main__":
if len(sys.argv) == 2:
package_dir = sys.argv[1]
else:
package_dir = os.path.join(
APTOS_CORE_PATH,
"aptos-move",
"move-examples",
"hello_blockchain",
)

asyncio.run(main(package_dir))

0 comments on commit fbd91ea

Please sign in to comment.