Skip to content

Commit

Permalink
Merge pull request #65 from MarshallBelles/flow-rust-sdk-m1
Browse files Browse the repository at this point in the history
Flow Rust SDK Milestone 1
  • Loading branch information
srinjoyc authored Oct 20, 2021
2 parents e7c5a2c + 97bd174 commit 4535e65
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
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
159 changes: 159 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,159 @@
## 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

0 comments on commit 4535e65

Please sign in to comment.