Remove height as a term

Unbeknowst to me, height doesn't have a universal definition of the 
chain length.

Bitcoin defines height as the block number, with getblockcount existing 
for the chain length.

Ethereum uses the unambiguous term "block number".

Monero defines height as both the block number and the chain length.

Instead of arguing about who's right, it's agreed it referring to both 
isn't productive. While we could provide our own definition, taking a 
side, moving to the unambiguous block number prevents future hiccups.

height is now only a term in the Monero code, where it takes its 
Monero-specific definition, as documented in the processor.
This commit is contained in:
Luke Parker
2022-10-15 21:39:06 -04:00
parent a245ee28c1
commit 514563cef0
5 changed files with 82 additions and 84 deletions

View File

@@ -206,7 +206,7 @@ impl Rpc {
self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0)) self.get_transactions(&[tx]).await.map(|mut txs| txs.swap_remove(0))
} }
pub async fn get_transaction_height(&self, tx: &[u8]) -> Result<usize, RpcError> { pub async fn get_transaction_block_number(&self, tx: &[u8]) -> Result<usize, RpcError> {
let txs: TransactionsResponse = let txs: TransactionsResponse =
self.rpc_call("get_transactions", Some(json!({ "txs_hashes": [hex::encode(tx)] }))).await?; self.rpc_call("get_transactions", Some(json!({ "txs_hashes": [hex::encode(tx)] }))).await?;

View File

@@ -47,8 +47,8 @@ pub trait Coin {
// Doesn't have to take self, enables some level of caching which is pleasant // Doesn't have to take self, enables some level of caching which is pleasant
fn address(&self, key: <Self::Curve as Curve>::G) -> Self::Address; fn address(&self, key: <Self::Curve as Curve>::G) -> Self::Address;
async fn get_height(&self) -> Result<usize, CoinError>; async fn get_latest_block_number(&self) -> Result<usize, CoinError>;
async fn get_block(&self, height: usize) -> Result<Self::Block, CoinError>; async fn get_block(&self, number: usize) -> Result<Self::Block, CoinError>;
async fn get_outputs( async fn get_outputs(
&self, &self,
block: &Self::Block, block: &Self::Block,
@@ -56,13 +56,13 @@ pub trait Coin {
) -> Result<Vec<Self::Output>, CoinError>; ) -> Result<Vec<Self::Output>, CoinError>;
// TODO: Remove // TODO: Remove
async fn is_confirmed(&self, tx: &[u8], height: usize) -> Result<bool, CoinError>; async fn is_confirmed(&self, tx: &[u8]) -> Result<bool, CoinError>;
async fn prepare_send( async fn prepare_send(
&self, &self,
keys: FrostKeys<Self::Curve>, keys: FrostKeys<Self::Curve>,
transcript: RecommendedTranscript, transcript: RecommendedTranscript,
height: usize, block_number: usize,
inputs: Vec<Self::Output>, inputs: Vec<Self::Output>,
payments: &[(Self::Address, u64)], payments: &[(Self::Address, u64)],
fee: Self::Fee, fee: Self::Fee,

View File

@@ -54,12 +54,13 @@ impl OutputTrait for Output {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct SignableTransaction( pub struct SignableTransaction {
FrostKeys<Ed25519>, keys: FrostKeys<Ed25519>,
RecommendedTranscript, transcript: RecommendedTranscript,
usize, // Monero height, defined as the length of the chain
MSignableTransaction, height: usize,
); actual: MSignableTransaction,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Monero { pub struct Monero {
@@ -121,12 +122,13 @@ impl Coin for Monero {
self.scanner(key).address() self.scanner(key).address()
} }
async fn get_height(&self) -> Result<usize, CoinError> { async fn get_latest_block_number(&self) -> Result<usize, CoinError> {
self.rpc.get_height().await.map_err(|_| CoinError::ConnectionError) // Monero defines height as chain length, so subtract 1 for block number
Ok(self.rpc.get_height().await.map_err(|_| CoinError::ConnectionError)? - 1)
} }
async fn get_block(&self, height: usize) -> Result<Self::Block, CoinError> { async fn get_block(&self, number: usize) -> Result<Self::Block, CoinError> {
self.rpc.get_block(height).await.map_err(|_| CoinError::ConnectionError) self.rpc.get_block(number).await.map_err(|_| CoinError::ConnectionError)
} }
async fn get_outputs( async fn get_outputs(
@@ -147,27 +149,27 @@ impl Coin for Monero {
) )
} }
async fn is_confirmed(&self, tx: &[u8], height: usize) -> Result<bool, CoinError> { async fn is_confirmed(&self, tx: &[u8]) -> Result<bool, CoinError> {
let tx_height = let tx_block_number =
self.rpc.get_transaction_height(tx).await.map_err(|_| CoinError::ConnectionError)?; self.rpc.get_transaction_block_number(tx).await.map_err(|_| CoinError::ConnectionError)?;
Ok((height.saturating_sub(tx_height) + 1) >= 10) Ok((self.get_latest_block_number().await?.saturating_sub(tx_block_number) + 1) >= 10)
} }
async fn prepare_send( async fn prepare_send(
&self, &self,
keys: FrostKeys<Ed25519>, keys: FrostKeys<Ed25519>,
transcript: RecommendedTranscript, transcript: RecommendedTranscript,
height: usize, block_number: usize,
mut inputs: Vec<Output>, mut inputs: Vec<Output>,
payments: &[(Address, u64)], payments: &[(Address, u64)],
fee: Fee, fee: Fee,
) -> Result<SignableTransaction, CoinError> { ) -> Result<SignableTransaction, CoinError> {
let spend = keys.group_key(); let spend = keys.group_key();
Ok(SignableTransaction( Ok(SignableTransaction {
keys, keys,
transcript, transcript,
height, height: block_number + 1,
MSignableTransaction::new( actual: MSignableTransaction::new(
self.rpc.get_protocol().await.unwrap(), // TODO: Make this deterministic self.rpc.get_protocol().await.unwrap(), // TODO: Make this deterministic
inputs.drain(..).map(|input| input.0).collect(), inputs.drain(..).map(|input| input.0).collect(),
payments.to_vec(), payments.to_vec(),
@@ -176,7 +178,7 @@ impl Coin for Monero {
fee, fee,
) )
.map_err(|_| CoinError::ConnectionError)?, .map_err(|_| CoinError::ConnectionError)?,
)) })
} }
async fn attempt_send( async fn attempt_send(
@@ -185,13 +187,13 @@ impl Coin for Monero {
included: &[u16], included: &[u16],
) -> Result<Self::TransactionMachine, CoinError> { ) -> Result<Self::TransactionMachine, CoinError> {
transaction transaction
.3 .actual
.clone() .clone()
.multisig( .multisig(
&self.rpc, &self.rpc,
transaction.0.clone(), transaction.keys.clone(),
transaction.1.clone(), transaction.transcript.clone(),
transaction.2, transaction.height,
included.to_vec(), included.to_vec(),
) )
.await .await
@@ -231,7 +233,7 @@ impl Coin for Monero {
async fn test_send(&self, address: Self::Address) { async fn test_send(&self, address: Self::Address) {
use rand_core::OsRng; use rand_core::OsRng;
let height = self.get_height().await.unwrap(); let new_block = self.get_latest_block_number().await.unwrap() + 1;
self.mine_block().await; self.mine_block().await;
for _ in 0 .. 7 { for _ in 0 .. 7 {
@@ -239,7 +241,7 @@ impl Coin for Monero {
} }
let outputs = Self::empty_scanner() let outputs = Self::empty_scanner()
.scan(&self.rpc, &self.rpc.get_block(height).await.unwrap()) .scan(&self.rpc, &self.rpc.get_block(new_block).await.unwrap())
.await .await
.unwrap() .unwrap()
.swap_remove(0) .swap_remove(0)

View File

@@ -59,9 +59,9 @@ impl Network for LocalNetwork {
} }
async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) { async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
// Mine a block so there's a confirmed height // Mine blocks so there's a confirmed block
coin.mine_block().await; coin.mine_block().await;
let height = coin.get_height().await.unwrap(); let latest = coin.get_latest_block_number().await.unwrap();
let mut keys = frost::tests::key_gen::<_, C::Curve>(&mut OsRng); let mut keys = frost::tests::key_gen::<_, C::Curve>(&mut OsRng);
let threshold = keys[&1].params().t(); let threshold = keys[&1].params().t();
@@ -70,13 +70,13 @@ async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
let mut wallets = vec![]; let mut wallets = vec![];
for i in 1 ..= threshold { for i in 1 ..= threshold {
let mut wallet = Wallet::new(MemCoinDb::new(), coin.clone()); let mut wallet = Wallet::new(MemCoinDb::new(), coin.clone());
wallet.acknowledge_height(0, height); wallet.acknowledge_block(0, latest);
wallet.add_keys(&WalletKeys::new(keys.remove(&i).unwrap(), 0)); wallet.add_keys(&WalletKeys::new(keys.remove(&i).unwrap(), 0));
wallets.push(wallet); wallets.push(wallet);
} }
// Get the chain to a height where blocks have sufficient confirmations // Get the chain to a length where blocks have sufficient confirmations
while (height + C::CONFIRMATIONS) > coin.get_height().await.unwrap() { while (latest + (C::CONFIRMATIONS - 1)) > coin.get_latest_block_number().await.unwrap() {
coin.mine_block().await; coin.mine_block().await;
} }
@@ -91,8 +91,8 @@ async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
for (network, wallet) in networks.iter_mut().zip(wallets.iter_mut()) { for (network, wallet) in networks.iter_mut().zip(wallets.iter_mut()) {
wallet.poll().await.unwrap(); wallet.poll().await.unwrap();
let height = coin.get_height().await.unwrap(); let latest = coin.get_latest_block_number().await.unwrap();
wallet.acknowledge_height(1, height - 10); wallet.acknowledge_block(1, latest - (C::CONFIRMATIONS - 1));
let signable = wallet let signable = wallet
.prepare_sends(1, vec![(wallet.address(), 10000000000)], fee) .prepare_sends(1, vec![(wallet.address(), 10000000000)], fee)
.await .await

View File

@@ -18,12 +18,12 @@ use crate::{
pub struct WalletKeys<C: Curve> { pub struct WalletKeys<C: Curve> {
keys: FrostKeys<C>, keys: FrostKeys<C>,
creation_height: usize, creation_block: usize,
} }
impl<C: Curve> WalletKeys<C> { impl<C: Curve> WalletKeys<C> {
pub fn new(keys: FrostKeys<C>, creation_height: usize) -> WalletKeys<C> { pub fn new(keys: FrostKeys<C>, creation_block: usize) -> WalletKeys<C> {
WalletKeys { keys, creation_height } WalletKeys { keys, creation_block }
} }
// Bind this key to a specific network by applying an additive offset // Bind this key to a specific network by applying an additive offset
@@ -45,42 +45,42 @@ impl<C: Curve> WalletKeys<C> {
} }
pub trait CoinDb { pub trait CoinDb {
// Set a height as scanned to // Set a block as scanned to
fn scanned_to_height(&mut self, height: usize); fn scanned_to_block(&mut self, block: usize);
// Acknowledge a given coin height for a canonical height // Acknowledge a specific block number as part of a canonical block
fn acknowledge_height(&mut self, canonical: usize, height: usize); fn acknowledge_block(&mut self, canonical: usize, block: usize);
// Adds an output to the DB. Returns false if the output was already added // Adds an output to the DB. Returns false if the output was already added
fn add_output<O: Output>(&mut self, output: &O) -> bool; fn add_output<O: Output>(&mut self, output: &O) -> bool;
// Height this coin has been scanned to // Block this coin has been scanned to (inclusive)
fn scanned_height(&self) -> usize; fn scanned_block(&self) -> usize;
// Acknowledged height for a given canonical height // Acknowledged block for a given canonical block
fn acknowledged_height(&self, canonical: usize) -> usize; fn acknowledged_block(&self, canonical: usize) -> usize;
} }
pub struct MemCoinDb { pub struct MemCoinDb {
// Height this coin has been scanned to // Height this coin has been scanned to
scanned_height: usize, scanned_block: usize,
// Acknowledged height for a given canonical height // Acknowledged block for a given canonical block
acknowledged_heights: HashMap<usize, usize>, acknowledged_blocks: HashMap<usize, usize>,
outputs: HashMap<Vec<u8>, Vec<u8>>, outputs: HashMap<Vec<u8>, Vec<u8>>,
} }
impl MemCoinDb { impl MemCoinDb {
pub fn new() -> MemCoinDb { pub fn new() -> MemCoinDb {
MemCoinDb { scanned_height: 0, acknowledged_heights: HashMap::new(), outputs: HashMap::new() } MemCoinDb { scanned_block: 0, acknowledged_blocks: HashMap::new(), outputs: HashMap::new() }
} }
} }
impl CoinDb for MemCoinDb { impl CoinDb for MemCoinDb {
fn scanned_to_height(&mut self, height: usize) { fn scanned_to_block(&mut self, block: usize) {
self.scanned_height = height; self.scanned_block = block;
} }
fn acknowledge_height(&mut self, canonical: usize, height: usize) { fn acknowledge_block(&mut self, canonical: usize, block: usize) {
debug_assert!(!self.acknowledged_heights.contains_key(&canonical)); debug_assert!(!self.acknowledged_blocks.contains_key(&canonical));
self.acknowledged_heights.insert(canonical, height); self.acknowledged_blocks.insert(canonical, block);
} }
fn add_output<O: Output>(&mut self, output: &O) -> bool { fn add_output<O: Output>(&mut self, output: &O) -> bool {
@@ -96,12 +96,12 @@ impl CoinDb for MemCoinDb {
true true
} }
fn scanned_height(&self) -> usize { fn scanned_block(&self) -> usize {
self.scanned_height self.scanned_block
} }
fn acknowledged_height(&self, canonical: usize) -> usize { fn acknowledged_block(&self, canonical: usize) -> usize {
self.acknowledged_heights[&canonical] self.acknowledged_blocks[&canonical]
} }
} }
@@ -212,22 +212,18 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
Wallet { db, coin, keys: vec![], pending: vec![] } Wallet { db, coin, keys: vec![], pending: vec![] }
} }
pub fn scanned_height(&self) -> usize { pub fn scanned_block(&self) -> usize {
self.db.scanned_height() self.db.scanned_block()
} }
pub fn acknowledge_height(&mut self, canonical: usize, height: usize) { pub fn acknowledge_block(&mut self, canonical: usize, block: usize) {
self.db.acknowledge_height(canonical, height); self.db.acknowledge_block(canonical, block);
if height > self.db.scanned_height() {
self.db.scanned_to_height(height);
} }
} pub fn acknowledged_block(&self, canonical: usize) -> usize {
pub fn acknowledged_height(&self, canonical: usize) -> usize { self.db.acknowledged_block(canonical)
self.db.acknowledged_height(canonical)
} }
pub fn add_keys(&mut self, keys: &WalletKeys<C::Curve>) { pub fn add_keys(&mut self, keys: &WalletKeys<C::Curve>) {
// Doesn't use +1 as this is height, not block index, and poll moves by block index self.pending.push((self.acknowledged_block(keys.creation_block), keys.bind(C::ID)));
self.pending.push((self.acknowledged_height(keys.creation_height), keys.bind(C::ID)));
} }
pub fn address(&self) -> C::Address { pub fn address(&self) -> C::Address {
@@ -236,17 +232,18 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
// TODO: Remove // TODO: Remove
pub async fn is_confirmed(&mut self, tx: &[u8]) -> Result<bool, CoinError> { pub async fn is_confirmed(&mut self, tx: &[u8]) -> Result<bool, CoinError> {
self.coin.is_confirmed(tx, self.scanned_height() + C::CONFIRMATIONS).await self.coin.is_confirmed(tx).await
} }
pub async fn poll(&mut self) -> Result<(), CoinError> { pub async fn poll(&mut self) -> Result<(), CoinError> {
if self.coin.get_height().await? < C::CONFIRMATIONS { if self.coin.get_latest_block_number().await? < (C::CONFIRMATIONS - 1) {
return Ok(()); return Ok(());
} }
let confirmed_block = self.coin.get_height().await? - C::CONFIRMATIONS; let confirmed_block = self.coin.get_latest_block_number().await? - (C::CONFIRMATIONS - 1);
for b in self.scanned_height() ..= confirmed_block { // Will never scan the genesis block, which shouldn't be an issue
// If any keys activated at this height, shift them over for b in (self.scanned_block() + 1) ..= confirmed_block {
// If any keys activated at this block, shift them over
{ {
let mut k = 0; let mut k = 0;
while k < self.pending.len() { while k < self.pending.len() {
@@ -274,8 +271,7 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
); );
} }
// Blocks are zero-indexed while heights aren't self.db.scanned_to_block(b);
self.db.scanned_to_height(b + 1);
} }
Ok(()) Ok(())
@@ -296,7 +292,7 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
return Ok((vec![], vec![])); return Ok((vec![], vec![]));
} }
let acknowledged_height = self.acknowledged_height(canonical); let acknowledged_block = self.acknowledged_block(canonical);
// TODO: Log schedule outputs when MAX_OUTPUTS is lower than payments.len() // TODO: Log schedule outputs when MAX_OUTPUTS is lower than payments.len()
// Payments is the first set of TXs in the schedule // Payments is the first set of TXs in the schedule
@@ -318,16 +314,16 @@ impl<D: CoinDb, C: Coin> Wallet<D, C> {
// Create the transcript for this transaction // Create the transcript for this transaction
let mut transcript = RecommendedTranscript::new(b"Serai Processor Wallet Send"); let mut transcript = RecommendedTranscript::new(b"Serai Processor Wallet Send");
transcript transcript
.append_message(b"canonical_height", &u64::try_from(canonical).unwrap().to_le_bytes()); .append_message(b"canonical_block", &u64::try_from(canonical).unwrap().to_le_bytes());
transcript.append_message( transcript.append_message(
b"acknowledged_height", b"acknowledged_block",
&u64::try_from(acknowledged_height).unwrap().to_le_bytes(), &u64::try_from(acknowledged_block).unwrap().to_le_bytes(),
); );
transcript.append_message(b"index", &u64::try_from(txs.len()).unwrap().to_le_bytes()); transcript.append_message(b"index", &u64::try_from(txs.len()).unwrap().to_le_bytes());
let tx = self let tx = self
.coin .coin
.prepare_send(keys.clone(), transcript, acknowledged_height, inputs, &outputs, fee) .prepare_send(keys.clone(), transcript, acknowledged_block, inputs, &outputs, fee)
.await?; .await?;
// self.db.save_tx(tx) // TODO // self.db.save_tx(tx) // TODO
txs.push(tx); txs.push(tx);