Skip to content

Commit

Permalink
Add ability to publish user moduels
Browse files Browse the repository at this point in the history
  • Loading branch information
SunMi Lee authored and bors-libra committed Sep 28, 2021
1 parent 06e8864 commit 5b2d80f
Show file tree
Hide file tree
Showing 17 changed files with 258 additions and 210 deletions.
41 changes: 14 additions & 27 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ members = [
"shuffle/cli",
"shuffle/genesis",
"shuffle/move",
"shuffle/sample-app",
"shuffle/transaction-builder",
"state-sync/inter-component/consensus-notifications",
"state-sync/inter-component/event-notifications",
Expand Down
27 changes: 0 additions & 27 deletions shuffle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ just started.
├── move/src # Move source code
│   ├── diem # Diem Framework code
│   ├── SampleModule.move # A sample Move module
├── sample-app # A sample app talking to the blockchain
├── transaction-builder # Auto-generated transaction builders in Rust
```
## Step 0: Install Dependencies
Expand Down Expand Up @@ -77,29 +76,3 @@ Building Move code ... thread 'main' panicked at 'Automatically building Move co
Need to manually resolve the issue using the CLI', shuffle/genesis/src/lib.rs:75:13
```
Go back to `./move` folder and recompile by running `compile.sh` again.

## Step 3: Send Transactions to the Validator

Make sure your node from step 2 is running before starting this step.

Inside `./sample-app`, run the following command:
```bash
cargo run -- --node-config-dir <config-dir-from-previous-step> --account-key-path new_account.key
```

The output of this command should look like this:
```
Connecting to http://0.0.0.0:8080...
Create a new account...Success!
Mint a coin to the new account...Success!
Mint another coin to the new account (this should fail)...
Error: TransactionExecutionFailed(
.... some detailed error message
```

This sample app submits three transactions to the validator node:
(1) creates a new account with the specified key at `account-key-path`, (2) publishes a `SampleModule::Coin` resource under the address of the
newly created account, (3) publish another `SampleModule::Coin` resource to the address of the same account.

The first two transactions should succeed and the last one should fail because you can only publish
one resource of each type under an address.
14 changes: 14 additions & 0 deletions shuffle/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,38 @@ edition = "2018"

[dependencies]
anyhow = "1.0.38"
bcs = "0.1.2"
once_cell = "1.7.2"
serde = { version = "1.0.124", features = ["derive"] }
serde_json = "1.0.64"
structopt = "0.3.21"
toml = "0.5.8"
walkdir = "2.3.1"

abigen = { path = "../../language/move-prover/abigen" }
diemdb = { path = "../../storage/diemdb" }
diem-config = { path = "../../config" }
diem-crypto = { path = "../../crypto/crypto" }
diem-framework = { path = "../../language/diem-framework" }
diem-framework-releases = { path = "../../language/diem-framework/DPN/releases" }
diem-genesis-tool = { path = "../../config/management/genesis" }
diem-json-rpc-types = { path = "../../json-rpc/types" }
diem-node = { path = "../../diem-node" }
diem-sdk = { path = "../../sdk" }
diem-temppath = { path = "../../common/temppath" }
diem-types = { path = "../../types" }
diem-workspace-hack = { path = "../../common/workspace-hack" }
generate-key = { path = "../../config/generate-key" }
move-cli = { path = "../../language/tools/move-cli" }
move-lang = { path = "../../language/move-lang" }
move-package = { path = "../../language/tools/move-package" }
move-stdlib = { path = "../../language/move-stdlib" }
move-binary-format = { path = "../../language/move-binary-format" }
move-command-line-common = { path = "../../language/move-command-line-common" }
move-prover = { path = "../../language/move-prover" }
shuffle-custom-node = { path = "../genesis" }
shuffle-transaction-builder = { path = "../transaction-builder" }
transaction-builder-generator = { path = "../../language/transaction-builder/generator" }

[[bin]]
name = "shuffle"
Expand Down
1 change: 1 addition & 0 deletions shuffle/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Please run `cargo run -p shuffle -- help`. Ultimately, this will become

1. `cargo run -p shuffle -- new /tmp/helloblockchain` creates a new shuffle project
2. `cargo run -p shuffle -- node /tmp/helloblockchain` runs node based on project
3. `cargo run -p shuffle -- deploy /tmp/helloblockchain` publishes a module to the created node

## Development

Expand Down
File renamed without changes.
217 changes: 217 additions & 0 deletions shuffle/cli/src/deploy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0

use anyhow::{anyhow, bail, format_err, Context, Result};
use diem_config::config::NodeConfig;
use diem_crypto::PrivateKey;
use diem_sdk::{
client::BlockingClient,
transaction_builder::{Currency, TransactionFactory},
types::{
account_address::AccountAddress,
transaction::{Module, TransactionPayload},
LocalAccount,
},
};
use diem_temppath::TempPath;
use diem_types::{
account_config, account_state::AccountState, account_state_blob::AccountStateBlob,
chain_id::ChainId, transaction::authenticator::AuthenticationKey,
};
use generate_key::load_key;
use move_binary_format::{file_format::CompiledModule, normalized};
use move_command_line_common::files::MOVE_EXTENSION;
use shuffle_transaction_builder::framework::{
encode_create_parent_vasp_account_script_function, encode_mint_coin_script_function,
};
use std::{
convert::TryFrom,
fs, io,
io::Write,
path::{Path, PathBuf},
process::Command,
};

pub fn handle(project_dir: PathBuf, account_key_path: PathBuf) -> Result<()> {
let config_path = project_dir.join("nodeconfig/0").join("node.yaml");
let config = NodeConfig::load(&config_path)
.with_context(|| format!("Failed to load NodeConfig from file: {:?}", config_path))?;
let root_key_path = project_dir.join("nodeconfig").join("mint.key");
let root_account_key = load_key(root_key_path);
let new_account_key = load_key(account_key_path);
let json_rpc_url = format!("http://0.0.0.0:{}", config.json_rpc.address.port());
let factory = TransactionFactory::new(ChainId::test());
println!("Connecting to {}...", json_rpc_url);

let client = BlockingClient::new(json_rpc_url);
let root_seq_num = client
.get_account(account_config::treasury_compliance_account_address())?
.into_inner()
.unwrap()
.sequence_number;
let mut root_account = LocalAccount::new(
account_config::treasury_compliance_account_address(),
root_account_key,
root_seq_num,
);
let mut new_account = LocalAccount::new(
AuthenticationKey::ed25519(&new_account_key.public_key()).derived_address(),
new_account_key,
0,
);
println!("======new account {}", new_account.address());

// Create a new account.
print!("Create a new ParentVASP account (we cannot create a regular account right now)...");
let create_new_account_txn = root_account.sign_with_transaction_builder(
TransactionFactory::new(ChainId::test()).payload(
encode_create_parent_vasp_account_script_function(
Currency::XUS.type_tag(),
0,
new_account.address(),
new_account.authentication_key().prefix().to_vec(),
vec![],
false,
),
),
);
send(&client, create_new_account_txn)?;
println!("Success!");

// Mint a coin to the newly created account.
print!("Mint a coin to the new account...");
let mint_coin_txn = new_account.sign_with_transaction_builder(
TransactionFactory::new(ChainId::test()).payload(encode_mint_coin_script_function(100)),
);
send(&client, mint_coin_txn)?;
println!("Success!");

// ================= Send a module transaction ========================
print!("Add a module to user account...");

// Get the path to the Move stdlib sources
let move_stdlib_dir = move_stdlib::move_stdlib_modules_full_path();
let diem_framework_dir = diem_framework::diem_core_modules_full_path();

let module_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join(project_dir)
.join("sources/SampleUserModule.move")
.canonicalize()?;
let copied_module_path =
copy_file_with_sender_address(&module_path, new_account.address()).unwrap();
let unwrapped_module_path = copied_module_path.to_str().unwrap();
let compiled_module = compile_program(
unwrapped_module_path,
&[move_stdlib_dir.as_str(), diem_framework_dir.as_str()],
)?;

let publish_txn = new_account.sign_with_transaction_builder(
factory.payload(TransactionPayload::Module(Module::new(compiled_module))),
);

send(&client, publish_txn)?;
println!("Success!");

// ================= Get modules in the account ========================

let account_state_blob: AccountStateBlob = {
let blob = client
.get_account_state_with_proof(new_account.address(), None, None)
.unwrap()
.into_inner()
.blob
.ok_or_else(|| anyhow::anyhow!("missing account state blob"))?;
bcs::from_bytes(&blob)?
};
let account_state = AccountState::try_from(&account_state_blob)?;
// println!("account_address {}", account_state.get_account_address().unwrap().unwrap());
let mut modules = vec![];
for module_bytes in account_state.get_modules() {
modules.push(normalized::Module::new(
&CompiledModule::deserialize(module_bytes)
.map_err(|e| anyhow!("Failure deserializing module: {:?}", e))?,
));
}
println!("move modules length: {}", modules.len());
println!("move modules name: {}", modules[0].name);

Ok(())
}

/// Send a transaction to the blockchain through the blocking client.
fn send(client: &BlockingClient, tx: diem_types::transaction::SignedTransaction) -> Result<()> {
use diem_json_rpc_types::views::VMStatusView;

client.submit(&tx)?;
assert_eq!(
client
.wait_for_signed_transaction(&tx, Some(std::time::Duration::from_secs(60)), None)?
.into_inner()
.vm_status,
VMStatusView::Executed,
);
Ok(())
}

/// Compile Move program
pub fn compile_program(file_path: &str, dependency_paths: &[&str]) -> Result<Vec<u8>> {
let tmp_output_dir = TempPath::new();
tmp_output_dir
.create_as_dir()
.expect("error creating temporary output directory");
let tmp_output_path = tmp_output_dir.as_ref().display().to_string();

let mut command = Command::new("cargo");
command
.args(&["run", "-p", "move-lang", "--bin", "move-build", "--"])
.arg(file_path)
.args(&["-o", &tmp_output_path]);

for dep in dependency_paths {
command.args(&["-d", dep]);
}
for (name, addr) in diem_framework::diem_framework_named_addresses() {
command.args(&["-a", &format!("{}=0x{:#X}", name, addr)]);
}

let output = command.output()?;
if !output.status.success() {
return Err(format_err!("compilation failed"));
}

let mut output_files = walkdir::WalkDir::new(tmp_output_path)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| {
let path = e.path();
e.file_type().is_file()
&& path
.extension()
.and_then(|s| s.to_str())
.map(|ext| ext == "mv")
.unwrap_or(false)
})
.filter_map(|e| e.path().to_str().map(|s| s.to_string()))
.collect::<Vec<_>>();
if output_files.is_empty() {
bail!("compiler failed to produce an output file")
}

let compiled_program = if output_files.len() != 1 {
bail!("compiler output has more than one file")
} else {
fs::read(output_files.pop().unwrap())?
};

Ok(compiled_program)
}

fn copy_file_with_sender_address(file_path: &Path, sender: AccountAddress) -> io::Result<PathBuf> {
let tmp_source_path = TempPath::new().as_ref().with_extension(MOVE_EXTENSION);
let mut tmp_source_file = std::fs::File::create(tmp_source_path.clone())?;
let mut code = fs::read_to_string(file_path)?;
code = code.replace("{{sender}}", &format!("0x{}", sender));
writeln!(tmp_source_file, "{}", code)?;
Ok(tmp_source_path)
}
Loading

0 comments on commit 5b2d80f

Please sign in to comment.