Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow Rust SDK Milestone 2 #66

Merged
merged 6 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions submissions/issue-20/milestone-1/flow-rust-sdk-team.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Flow-Rust-SDK - Milestone 1

This PR is for issue #20.



### Milestone 1 Completion:
- 1 [x] Implement the gRPC layer of the SDK, allowing communication with the Flow blockchain

Inside the [lib.rs](https://github.com/MarshallBelles/flow-rust-sdk/blob/release/src/lib.rs) a gRPC client has been defined as a struct on line 38: `FlowConnection` which accepts a network transport layer of type `<T>`. A default implementation for type `tonic::transport::Channel` is implemented starting on line 43, using the [Tonic gRPC library](https://crates.io/crates/tonic) to facilitate communications.

You can test this connection by instantiating the struct with the helper function `FlowConnection::new("grpc://address");`.

A more full example of usage:
```rs
use flow_rust_sdk::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// instantiate a new connection:
let mut connection = FlowConnection::new("grpc://localhost:3569").await?;
// Substitute emulator service account address as the payer, and keys if needed.
let payer = "f8d6e0586b0a20c7";
let payer_private_key = "324db577a741a9b7a2eb6cef4e37e72ff01a554bdbe4bd77ef9afe1cb00d3cec";
let public_keys = vec!["ef100c2a8d04de602cd59897e08001cf57ca153cb6f9083918cde1ec7de77418a2c236f7899b3f786d08a1b4592735e3a7461c3e933f420cf9babe350abe0c5a".to_owned()];

// Use the connection defined above to send a transaction to the blockchain, created and signed within the helper function `create_account`
let acct = connection.create_account(
public_keys.to_vec(),
&payer.to_owned(),
&payer_private_key.to_owned(),
0,
)
.await
.expect("Could not create account");
println!("new account address: {:?}", hex::encode(acct.address));
Ok(())
}
```

Authors include:

@marshallbelles
@bluesign
160 changes: 160 additions & 0 deletions submissions/issue-20/milestone-2/flow-rust-sdk-team.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
## Flow-Rust-SDK - Milestone 2

This PR is for issue #20.



### Milestone 2 Completion:
- 2: Accomplish transaction signing in a way that handles the complex algorithm / hashing / encoding for the user.

Transactional signing and RLP encoding turned out to be the most difficult and time-consuming task of this project.
@bluesign provided a significant amount of help by sharing knowledge gained while working on his Swift SDK.

Transaction signing is implemented in the Flow-Rust-SDK lib.rs [starting on line 466](https://github.com/MarshallBelles/flow-rust-sdk/blob/344d7d8f7aa2aa67c47455e54bedf4723138a981/src/lib.rs#L466).

The transaction payload RLP encoding takes place [on line 414](https://github.com/MarshallBelles/flow-rust-sdk/blob/344d7d8f7aa2aa67c47455e54bedf4723138a981/src/lib.rs#L414) and the transaction envelope is encoded [on line 370](https://github.com/MarshallBelles/flow-rust-sdk/blob/344d7d8f7aa2aa67c47455e54bedf4723138a981/src/lib.rs#L370).


Before signing a transaction, you must first define and build one. This is demonstrated in the `create_account` function starting [on line 176](https://github.com/MarshallBelles/flow-rust-sdk/blob/344d7d8f7aa2aa67c47455e54bedf4723138a981/src/lib.rs#L176):
```rs
// The following is copied from the library.
// To execute the code shown below, copy the example at: https://github.com/MarshallBelles/flow-rust-sdk-example/blob/main/src/main.rs
// our default values passed into the transaction
let payer = "f8d6e0586b0a20c7";
let payer_private_key = "324db577a741a9b7a2eb6cef4e37e72ff01a554bdbe4bd77ef9afe1cb00d3cec";
let public_keys = vec!["ef100c2a8d04de602cd59897e08001cf57ca153cb6f9083918cde1ec7de77418a2c236f7899b3f786d08a1b4592735e3a7461c3e933f420cf9babe350abe0c5a".to_owned()];

// Define the transaction to be executed.
// Here we are using AuthAccount to create a new account and assign keys
let create_account_template = b"
transaction(publicKeys: [String], contracts: {String: String}) {
prepare(signer: AuthAccount) {
let acct = AuthAccount(payer: signer)

for key in publicKeys {
acct.addPublicKey(key.decodeHex())
}

for contract in contracts.keys {
acct.contracts.add(name: contract, code: contracts[contract]!.decodeHex())
}
}
}";

// we get the latest block for the transaction
let latest_block: BlockResponse = self.get_block(None, None, Some(false)).await?;
// and then we need to get the authorizer account
let account: flow::Account = self.get_account(payer.clone())
.await?
.account
.unwrap();
// because then we need the proposal key details
let proposer = TransactionProposalKey {
address: hex::decode(payer).unwrap(),
key_id,
sequence_number: account.keys[key_id as usize].sequence_number as u64,
};
// here we process the key arguments
let keys_arg = process_keys_args(account_keys);
// empty contracts for this example
let contracts_arg = Argument::dictionary(vec![]);
// the following two lines handles the arguments and turns them into JSON strings for the transaction.
let keys_arg = json!(keys_arg);
let contracts_arg = json!(contracts_arg);
// build the transaction
let transaction: Transaction = build_transaction(
create_account_template.to_vec(),
vec![
serde_json::to_vec(&keys_arg)?,
serde_json::to_vec(&contracts_arg)?,
],
latest_block.block.unwrap().id,
1000,
proposer,
vec![payer.clone()],
payer.clone(),
)
.await?;

// After building the transaction, we need to sign the transaction.
// Here we define the signature
let signature = Sign {
address: payer.clone(),
key_id,
private_key: payer_private_key.clone(),
};

// now we can sign the transaction
let transaction: Option<Transaction> = sign_transaction(transaction, vec![], vec![&signature]).await?;

// and send it to the blockchain
let transaction: SendTransactionResponse = self.send_transaction(transaction).await?;

// from here the library polls the blockchain for the transaction result
```

The part above where we `sign_transaction` looks like this:
```rs
pub async fn sign_transaction(
built_transaction: Transaction,
payload_signatures: Vec<&Sign>,
envelope_signatures: Vec<&Sign>,
) -> Result<Option<Transaction>, Box<dyn error::Error>> {
let mut payload: Vec<TransactionSignature> = vec![];
let mut envelope: Vec<TransactionSignature> = vec![];
// for each of the payload private keys, sign the transaction
for signer in payload_signatures {
let encoded_payload: &[u8] = &payload_from_transaction(built_transaction.clone());
let mut domain_tag: Vec<u8> = b"FLOW-V0.0-transaction".to_vec();
// we need to pad 0s at the end of the domain_tag
padding(&mut domain_tag, 32);

let fully_encoded: Vec<u8> = [&domain_tag, encoded_payload].concat();
let mut addr = hex::decode(signer.address.clone()).unwrap();
padding(&mut addr, 8);

payload.push(TransactionSignature {
address: addr,
key_id: signer.key_id,
signature: sign(fully_encoded, signer.private_key.clone())?,
});
}
// for each of the envelope private keys, sign the transaction
for signer in envelope_signatures {
let encoded_payload: &[u8] =
&envelope_from_transaction(built_transaction.clone(), &payload);
let mut domain_tag: Vec<u8> = b"FLOW-V0.0-transaction".to_vec();
// we need to pad 0s at the end of the domain_tag
padding(&mut domain_tag, 32);

let fully_encoded: Vec<u8> = [&domain_tag, encoded_payload].concat();
let mut addr = hex::decode(signer.address.clone()).unwrap();
padding(&mut addr, 8);

envelope.push(TransactionSignature {
address: addr,
key_id: signer.key_id,
signature: sign(fully_encoded, signer.private_key.clone())?,
});
}
let signed_transaction = Some(Transaction {
script: built_transaction.script,
arguments: built_transaction.arguments,
reference_block_id: built_transaction.reference_block_id,
gas_limit: built_transaction.gas_limit,
proposal_key: built_transaction.proposal_key,
authorizers: built_transaction.authorizers,
payload_signatures: payload,
envelope_signatures: envelope,
payer: built_transaction.payer,
});
Ok(signed_transaction)
}
```

You can see in the snippet above that both payload and envelope signatures are handled for the user in a manner that reduces the complexity and does not require an advanced understanding of RLP encoding.

Authors include:

@marshallbelles
@bluesign
44 changes: 44 additions & 0 deletions submissions/issue-20/milestone-3/flow-rust-sdk-team.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Flow-Rust-SDK - Milestone 3

This PR is for issue #20.


### Milestone 3 Completion:
- 3 [x] Meet and exceed Flow SDK guidelines

The Flow SDK Guidelines are defined [here](https://github.com/onflow/sdks).

Each of the provided stories have examples:

Blocks:
- [Retrieve a block by ID](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Retrieve-a-block-by-ID)
- [Retrieve a block by height](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Retrieve-a-block-by-height)
- [Retrieve the latest block](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Retrieve-the-latest-block)

Collections:
- [Retrieve a collection by ID](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Retrieve-a-collection-by-ID)

Events:
- [Retrieve events by name in the block height range](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Retrieve-events-by-name-in-the-block-height-range)

Scripts:
- [Submit a script and parse the response](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Submit-a-script-and-parse-the-response)
- [Submit a script with arguments and parse the response](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Submit-a-script-with-arguments-and-parse-the-response)

Accounts:
- [Retrieve an account by address](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Retrieve-an-account-by-address)
- [Create a new account](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Create-a-new-account)
- [Deploy a new contract to the account](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Deploy-a-new-contract-to-the-account)
- [Remove a contract from the account](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Remove-a-contract-from-the-account)
- [Update an existing contract on the account](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Update-an-existing-contract-on-the-account)

Transactions:
- [Retrieve a transaction by ID](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Transactions)
- [Sign a transaction (single payer, proposer, authorizer or combination of multiple)](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Transactions)
- [Submit a signed transaction](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Transactions)
- [Sign a transaction with arguments and submit it](https://github.com/MarshallBelles/flow-rust-sdk/wiki/Transactions)

Authors include:

@marshallbelles
@bluesign
14 changes: 14 additions & 0 deletions submissions/issue-20/milestone-4/flow-rust-sdk-team.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Flow-Rust-SDK - Milestone 4 [WIP]

This PR is for issue #20.


### Milestone 4 Completion:
- 4 [ ] Complete documentation and common usage examples are available

This is a WIP.

Authors include:

@marshallbelles
@bluesign