-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #65 from MarshallBelles/flow-rust-sdk-m1
Flow Rust SDK Milestone 1
- Loading branch information
Showing
4 changed files
with
261 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |