Skip to main content

nym_mixnet_contract_common/
gateway.rs

1// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{IdentityKey, NodeId, SphinxKey};
5use cosmwasm_schema::cw_serde;
6use cosmwasm_std::{Addr, Coin, to_json_string};
7use std::cmp::Ordering;
8use std::fmt::Display;
9
10/// Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.
11#[cw_serde]
12#[derive(PartialOrd)]
13#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
14pub struct Gateway {
15    /// Network address of this gateway, for example 1.1.1.1 or foo.gateway.com
16    pub host: String,
17
18    /// Port used by this gateway for listening for mix packets.
19    pub mix_port: u16,
20
21    /// Port used by this gateway for listening for client requests.
22    pub clients_port: u16,
23
24    /// The physical, self-reported, location of this gateway.
25    // this field should be deprecated in favour of externally hosted information, like the mixnodes'.
26    pub location: String,
27
28    /// Base58-encoded x25519 public key used for sphinx key derivation.
29    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
30    pub sphinx_key: SphinxKey,
31
32    /// Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients
33    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
34    pub identity_key: IdentityKey,
35
36    /// The self-reported semver version of this gateway.
37    pub version: String,
38}
39
40/// Basic gateway information provided by the node operator.
41#[cw_serde]
42#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
43pub struct GatewayBond {
44    /// Original amount pledged by the operator of this node.
45    #[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
46    pub pledge_amount: Coin,
47
48    /// Address of the owner of this gateway.
49    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
50    pub owner: Addr,
51
52    /// Block height at which this gateway has been bonded.
53    pub block_height: u64,
54
55    /// Information provided by the operator for the purposes of bonding.
56    pub gateway: Gateway,
57
58    /// Entity who bonded this gateway on behalf of the owner.
59    /// If exists, it's most likely the address of the vesting contract.
60    #[cfg_attr(feature = "utoipa", schema(value_type = String))]
61    pub proxy: Option<Addr>,
62}
63
64impl GatewayBond {
65    pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
66        GatewayBond {
67            pledge_amount,
68            owner,
69            block_height,
70            gateway,
71            proxy: None,
72        }
73    }
74
75    pub fn identity(&self) -> &String {
76        &self.gateway.identity_key
77    }
78
79    pub fn pledge_amount(&self) -> Coin {
80        self.pledge_amount.clone()
81    }
82
83    pub fn owner(&self) -> &Addr {
84        &self.owner
85    }
86
87    pub fn gateway(&self) -> &Gateway {
88        &self.gateway
89    }
90}
91
92impl PartialOrd for GatewayBond {
93    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94        // first remove invalid cases
95        if self.pledge_amount.denom != other.pledge_amount.denom {
96            return None;
97        }
98
99        // try to order by total pledge
100        let pledge_cmp = self
101            .pledge_amount
102            .amount
103            .partial_cmp(&other.pledge_amount.amount)?;
104        if pledge_cmp != Ordering::Equal {
105            return Some(pledge_cmp);
106        }
107
108        // then check block height
109        let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
110        if height_cmp != Ordering::Equal {
111            return Some(height_cmp);
112        }
113
114        // finally go by the rest of the fields in order. It doesn't really matter at this point
115        // but we should be deterministic.
116        let owner_cmp = self.owner.partial_cmp(&other.owner)?;
117        if owner_cmp != Ordering::Equal {
118            return Some(owner_cmp);
119        }
120
121        self.gateway.partial_cmp(&other.gateway)
122    }
123}
124
125impl Display for GatewayBond {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        write!(
128            f,
129            "amount: {} {}, owner: {}, identity: {}",
130            self.pledge_amount.amount,
131            self.pledge_amount.denom,
132            self.owner,
133            self.gateway.identity_key
134        )
135    }
136}
137
138#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
139#[cfg_attr(
140    feature = "generate-ts",
141    ts(
142        export,
143        export_to = "ts-packages/types/src/types/rust/GatewayConfigUpdate.ts"
144    )
145)]
146#[cw_serde]
147pub struct GatewayConfigUpdate {
148    pub host: String,
149    pub mix_port: u16,
150    pub clients_port: u16,
151    pub location: String,
152    pub version: String,
153}
154
155impl GatewayConfigUpdate {
156    pub fn to_inline_json(&self) -> String {
157        to_json_string(self).unwrap_or_else(|_| "serialisation failure".into())
158    }
159}
160
161/// Response containing paged list of all gateway bonds in the contract.
162#[cw_serde]
163pub struct PagedGatewayResponse {
164    /// The gateway bond information present in the contract.
165    pub nodes: Vec<GatewayBond>,
166
167    /// Maximum number of entries that could be included in a response. `per_page <= nodes.len()`
168    // this field is rather redundant and should be deprecated.
169    pub per_page: usize,
170
171    /// Field indicating paging information for the following queries if the caller wishes to get further entries.
172    pub start_next_after: Option<IdentityKey>,
173}
174
175impl PagedGatewayResponse {
176    pub fn new(
177        nodes: Vec<GatewayBond>,
178        per_page: usize,
179        start_next_after: Option<IdentityKey>,
180    ) -> Self {
181        PagedGatewayResponse {
182            nodes,
183            per_page,
184            start_next_after,
185        }
186    }
187}
188
189/// Response containing details of a gateway belonging to the particular owner.
190#[cw_serde]
191pub struct GatewayOwnershipResponse {
192    /// Validated address of the gateway owner.
193    pub address: Addr,
194
195    /// If the provided address owns a gateway, this field contains its details.
196    pub gateway: Option<GatewayBond>,
197}
198
199/// Response containing details of a gateway with the provided identity key.
200#[cw_serde]
201pub struct GatewayBondResponse {
202    /// The identity key (base58-encoded ed25519 public key) of the gateway.
203    pub identity: IdentityKey,
204
205    /// If there exists a gateway with the provided identity key, this field contains its details.
206    pub gateway: Option<GatewayBond>,
207}
208
209#[cw_serde]
210pub struct PreassignedId {
211    /// The identity key (base58-encoded ed25519 public key) of the gateway.
212    pub identity: IdentityKey,
213
214    /// The id pre-assigned to this gateway
215    pub node_id: NodeId,
216}
217
218#[cw_serde]
219pub struct PreassignedGatewayIdsResponse {
220    pub ids: Vec<PreassignedId>,
221
222    /// Field indicating paging information for the following queries if the caller wishes to get further entries.
223    pub start_next_after: Option<IdentityKey>,
224}
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    fn gateway_fixture() -> Gateway {
231        Gateway {
232            host: "1.1.1.1".to_string(),
233            mix_port: 123,
234            clients_port: 456,
235            location: "foomplandia".to_string(),
236            sphinx_key: "sphinxkey".to_string(),
237            identity_key: "identitykey".to_string(),
238            version: "0.11.0".to_string(),
239        }
240    }
241
242    #[test]
243    fn gateway_bond_partial_ord() {
244        let _150foos = Coin::new(150u32, "foo");
245        let _140foos = Coin::new(140u32, "foo");
246        let _50foos = Coin::new(50u32, "foo");
247        let _0foos = Coin::new(0u32, "foo");
248
249        let gate1 = GatewayBond {
250            pledge_amount: _150foos.clone(),
251            owner: Addr::unchecked("foo1"),
252            block_height: 100,
253            gateway: gateway_fixture(),
254            proxy: None,
255        };
256
257        let gate2 = GatewayBond {
258            pledge_amount: _150foos,
259            owner: Addr::unchecked("foo2"),
260            block_height: 120,
261            gateway: gateway_fixture(),
262            proxy: None,
263        };
264
265        let gate3 = GatewayBond {
266            pledge_amount: _50foos,
267            owner: Addr::unchecked("foo3"),
268            block_height: 120,
269            gateway: gateway_fixture(),
270            proxy: None,
271        };
272
273        let gate4 = GatewayBond {
274            pledge_amount: _140foos,
275            owner: Addr::unchecked("foo4"),
276            block_height: 120,
277            gateway: gateway_fixture(),
278            proxy: None,
279        };
280
281        let gate5 = GatewayBond {
282            pledge_amount: _0foos,
283            owner: Addr::unchecked("foo5"),
284            block_height: 120,
285            gateway: gateway_fixture(),
286            proxy: None,
287        };
288
289        // summary:
290        // gate1: 150bond, foo1, 100
291        // gate2: 150bond, foo2, 120
292        // gate3: 50bond, foo3, 120
293        // gate4: 140bond, foo4, 120
294        // gate5: 0bond, foo5, 120
295
296        // highest total bond is used
297        // finally just the rest of the fields
298
299        // gate1 has higher total than gate4 or gate5
300        assert!(gate1 > gate4);
301        assert!(gate1 > gate5);
302
303        // gate1 has the same total as gate3, however, gate1 has more tokens in bond
304        assert!(gate1 > gate3);
305        // same case for gate4 and gate5
306        assert!(gate4 > gate5);
307
308        // same bond and delegation, so it's just ordered by height
309        assert!(gate1 < gate2);
310    }
311}