Skip to main content

cosmos_tx/
builder.rs

1//! Protocol Buffer-encoded transaction builder
2
3// Includes code originally from ibc-rs:
4// <https://github.com/informalsystems/ibc-rs>
5// Copyright © 2020 Informal Systems Inc.
6// Licensed under the Apache 2.0 license
7
8use super::msg::Msg;
9use crate::{Signature, VerifyingKey};
10use cosmos_sdk_proto::cosmos::tx::v1beta1::{
11    mode_info, AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, TxBody, TxRaw,
12};
13use ecdsa::signature::Signer;
14use eyre::Result;
15use tendermint::{block, chain};
16
17/// Protocol Buffer-encoded transaction builder
18pub struct Builder {
19    /// Chain ID
20    chain_id: chain::Id,
21
22    /// Account number to include in transactions
23    account_number: u64,
24}
25
26impl Builder {
27    /// Create a new transaction builder
28    pub fn new(chain_id: impl Into<chain::Id>, account_number: u64) -> Self {
29        Self {
30            chain_id: chain_id.into(),
31            account_number,
32        }
33    }
34
35    /// Borrow this transaction builder's chain ID
36    pub fn chain_id(&self) -> &chain::Id {
37        &self.chain_id
38    }
39
40    /// Get this transaction builder's account number
41    pub fn account_number(&self) -> u64 {
42        self.account_number
43    }
44
45    /// Build and sign a transaction containing the given messages
46    pub fn sign_tx<S>(
47        &self,
48        signer: &S,
49        sequence: u64,
50        messages: &[Msg],
51        fee: Fee,
52        memo: impl Into<String>,
53        timeout_height: block::Height,
54    ) -> Result<Vec<u8>>
55    where
56        S: Signer<Signature>,
57        VerifyingKey: for<'a> From<&'a S>,
58    {
59        // Create TxBody
60        let body = TxBody {
61            messages: messages.iter().map(|msg| msg.0.clone()).collect(),
62            memo: memo.into(),
63            timeout_height: timeout_height.into(),
64            extension_options: Default::default(),
65            non_critical_extension_options: Default::default(),
66        };
67
68        // A protobuf serialization of a TxBody
69        let mut body_buf = Vec::new();
70        prost::Message::encode(&body, &mut body_buf).unwrap();
71
72        let pk = VerifyingKey::from(signer);
73        let mut pk_buf = Vec::new();
74        prost::Message::encode(&pk.to_bytes().to_vec(), &mut pk_buf).unwrap();
75
76        // TODO(tarcieri): extract proper key type
77        let pk_any = prost_types::Any {
78            type_url: "/cosmos.crypto.secp256k1.PubKey".to_string(),
79            value: Vec::from(&pk.to_bytes()[..]),
80        };
81
82        let single = mode_info::Single { mode: 1 };
83
84        let mode = Some(ModeInfo {
85            sum: Some(mode_info::Sum::Single(single)),
86        });
87
88        let signer_info = SignerInfo {
89            public_key: Some(pk_any),
90            mode_info: mode,
91            sequence,
92        };
93
94        let auth_info = AuthInfo {
95            signer_infos: vec![signer_info],
96            fee: Some(fee),
97        };
98
99        // Protobuf serialization of `AuthInfo`
100        let mut auth_buf = Vec::new();
101        prost::Message::encode(&auth_info, &mut auth_buf)?;
102
103        let sign_doc = SignDoc {
104            body_bytes: body_buf.clone(),
105            auth_info_bytes: auth_buf.clone(),
106            chain_id: self.chain_id.to_string(),
107            account_number: self.account_number,
108        };
109
110        // Protobuf serialization of `SignDoc`
111        let mut signdoc_buf = Vec::new();
112        prost::Message::encode(&sign_doc, &mut signdoc_buf)?;
113
114        // Sign the signdoc
115        let signed = signer.sign(&signdoc_buf);
116
117        let tx_raw = TxRaw {
118            body_bytes: body_buf,
119            auth_info_bytes: auth_buf,
120            signatures: vec![signed.as_ref().to_vec()],
121        };
122
123        let mut txraw_buf = Vec::new();
124        prost::Message::encode(&tx_raw, &mut txraw_buf)?;
125
126        Ok(txraw_buf)
127    }
128}