diff --git a/submissions/issue-20/milestone-1/flow-rust-sdk-team.md b/submissions/issue-20/milestone-1/flow-rust-sdk-team.md new file mode 100644 index 00000000..88e69ff8 --- /dev/null +++ b/submissions/issue-20/milestone-1/flow-rust-sdk-team.md @@ -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 ``. 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> { + // 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 diff --git a/submissions/issue-20/milestone-2/flow-rust-sdk-team.md b/submissions/issue-20/milestone-2/flow-rust-sdk-team.md new file mode 100644 index 00000000..8c265893 --- /dev/null +++ b/submissions/issue-20/milestone-2/flow-rust-sdk-team.md @@ -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 = 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, Box> { + let mut payload: Vec = vec![]; + let mut envelope: Vec = 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 = 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 = [&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 = 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 = [&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 diff --git a/submissions/issue-20/milestone-3/flow-rust-sdk-team.md b/submissions/issue-20/milestone-3/flow-rust-sdk-team.md new file mode 100644 index 00000000..1d7fa4b2 --- /dev/null +++ b/submissions/issue-20/milestone-3/flow-rust-sdk-team.md @@ -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 diff --git a/submissions/issue-20/milestone-4/flow-rust-sdk-team.md b/submissions/issue-20/milestone-4/flow-rust-sdk-team.md new file mode 100644 index 00000000..0de92103 --- /dev/null +++ b/submissions/issue-20/milestone-4/flow-rust-sdk-team.md @@ -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