From 300aeda09fd8ad5536a6cd6423ed28f112db4923 Mon Sep 17 00:00:00 2001 From: Jude Nelson Date: Wed, 31 Jan 2024 00:12:10 -0500 Subject: [PATCH] chore: expand test coverage for inventory generation using the new declarative nakamoto chain history test framework --- stackslib/src/net/tests/inv/nakamoto.rs | 371 ++++++++++++++++++++---- 1 file changed, 317 insertions(+), 54 deletions(-) diff --git a/stackslib/src/net/tests/inv/nakamoto.rs b/stackslib/src/net/tests/inv/nakamoto.rs index f962123d1..e622fd728 100644 --- a/stackslib/src/net/tests/inv/nakamoto.rs +++ b/stackslib/src/net/tests/inv/nakamoto.rs @@ -20,8 +20,9 @@ use std::sync::mpsc::sync_channel; use std::thread; use std::thread::JoinHandle; +use stacks_common::address::{AddressHashMode, C32_ADDRESS_VERSION_TESTNET_SINGLESIG}; use stacks_common::codec::{read_next, StacksMessageCodec}; -use stacks_common::types::chainstate::StacksPrivateKey; +use stacks_common::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey}; use stacks_common::types::StacksEpoch; use crate::chainstate::burn::db::sortdb::SortitionDB; @@ -33,20 +34,27 @@ use crate::chainstate::nakamoto::coordinator::tests::{ }; use crate::chainstate::nakamoto::NakamotoChainState; use crate::chainstate::stacks::db::StacksChainState; +use crate::chainstate::stacks::{ + StacksTransaction, StacksTransactionSigner, TokenTransferMemo, TransactionAnchorMode, + TransactionAuth, TransactionPayload, TransactionVersion, +}; +use crate::clarity::vm::types::StacksAddressExtensions; use crate::core::StacksEpochExtension; use crate::net::inv::nakamoto::InvGenerator; -use crate::net::test::TestPeer; +use crate::net::test::{TestEventObserver, TestPeer}; +use crate::net::tests::{NakamotoBootPlan, NakamotoBootStep, NakamotoBootTenure}; use crate::net::{ Error as NetError, GetNakamotoInvData, HandshakeData, NakamotoInvData, StacksMessage, StacksMessageType, }; +use crate::stacks_common::types::Address; use crate::util_lib::db::Error as DBError; /// Handshake with and get the reward cycle inventories for a range of reward cycles -fn peer_get_nakamoto_invs( - mut peer: TestPeer<'static>, +fn peer_get_nakamoto_invs<'a>( + mut peer: TestPeer<'a>, reward_cycles: &[u64], -) -> (TestPeer<'static>, Vec) { +) -> (TestPeer<'a>, Vec) { let privk = StacksPrivateKey::new(); let mut convo = peer.make_client_convo(); let client_peer = peer.make_client_local_peer(privk.clone()); @@ -80,63 +88,65 @@ fn peer_get_nakamoto_invs( let (shutdown_send, shutdown_recv) = sync_channel(1); let join_handle = thread::spawn(move || { - loop { - peer.step_with_ibd(false).unwrap(); - if let Ok(..) = shutdown_recv.try_recv() { - break; - } - } - peer - }); + let mut tcp_socket = TcpStream::connect(peer_addr).unwrap(); - let mut tcp_socket = TcpStream::connect(peer_addr).unwrap(); - - // first, handshake - let handshake_data = StacksMessageType::Handshake(HandshakeData::from_local_peer(&client_peer)); - let signed_handshake_data = convo - .sign_message(&chain_view, &privk, handshake_data) - .unwrap(); - signed_handshake_data - .consensus_serialize(&mut tcp_socket) - .unwrap(); - - // read back handshake-accept - let msg: StacksMessage = read_next(&mut tcp_socket).unwrap(); - match msg.payload { - StacksMessageType::HandshakeAccept(..) - | StacksMessageType::StackerDBHandshakeAccept(..) => {} - x => { - error!("Peer returned {:?}", &x); - panic!(); - } - } - - let mut replies = vec![]; - for get_nakamoto_inv in get_nakamoto_invs { - // send getnakamotoinv - get_nakamoto_inv + // first, handshake + let handshake_data = + StacksMessageType::Handshake(HandshakeData::from_local_peer(&client_peer)); + let signed_handshake_data = convo + .sign_message(&chain_view, &privk, handshake_data) + .unwrap(); + signed_handshake_data .consensus_serialize(&mut tcp_socket) .unwrap(); - loop { - // read back the message - let msg: StacksMessage = read_next(&mut tcp_socket).unwrap(); - let is_inv_reply = if let StacksMessageType::NakamotoInv(..) = &msg.payload { - true - } else { - false - }; - if is_inv_reply { - replies.push(msg.payload); - break; - } else { - debug!("Got spurious meessage {:?}", &msg); + // read back handshake-accept + let msg: StacksMessage = read_next(&mut tcp_socket).unwrap(); + match msg.payload { + StacksMessageType::HandshakeAccept(..) + | StacksMessageType::StackerDBHandshakeAccept(..) => {} + x => { + error!("Peer returned {:?}", &x); + panic!(); } } + + let mut replies = vec![]; + for get_nakamoto_inv in get_nakamoto_invs { + // send getnakamotoinv + get_nakamoto_inv + .consensus_serialize(&mut tcp_socket) + .unwrap(); + + loop { + // read back the message + let msg: StacksMessage = read_next(&mut tcp_socket).unwrap(); + let is_inv_reply = if let StacksMessageType::NakamotoInv(..) = &msg.payload { + true + } else { + false + }; + if is_inv_reply { + replies.push(msg.payload); + break; + } else { + debug!("Got spurious meessage {:?}", &msg); + } + } + } + + shutdown_send.send(true).unwrap(); + replies + }); + + loop { + peer.step_with_ibd(false).unwrap(); + if let Ok(..) = shutdown_recv.try_recv() { + break; + } } - shutdown_send.send(true).unwrap(); - let peer = join_handle.join().unwrap(); + let replies = join_handle.join().unwrap(); (peer, replies) } @@ -300,3 +310,256 @@ fn test_nakamoto_inv_10_extended_tenures_10_sortitions() { assert_eq!(bitvec.len() as u16, inv.bitlen); } } + +fn make_nakamoto_peer_from_invs<'a>( + test_name: &str, + observer: &'a TestEventObserver, + rc_len: u32, + prepare_len: u32, + bitvecs: Vec>, +) -> TestPeer<'a> { + for bitvec in bitvecs.iter() { + assert_eq!(bitvec.len() as u32, rc_len); + } + + let private_key = StacksPrivateKey::from_seed(&[2]); + let addr = StacksAddress::from_public_keys( + C32_ADDRESS_VERSION_TESTNET_SINGLESIG, + &AddressHashMode::SerializeP2PKH, + 1, + &vec![StacksPublicKey::from_private(&private_key)], + ) + .unwrap(); + let recipient_addr = + StacksAddress::from_string("ST2YM3J4KQK09V670TD6ZZ1XYNYCNGCWCVTASN5VM").unwrap(); + + let mut sender_nonce = 0; + + let mut next_stx_transfer = || { + let mut stx_transfer = StacksTransaction::new( + TransactionVersion::Testnet, + TransactionAuth::from_p2pkh(&private_key).unwrap(), + TransactionPayload::TokenTransfer( + recipient_addr.clone().to_account_principal(), + 1, + TokenTransferMemo([0x00; 34]), + ), + ); + stx_transfer.chain_id = 0x80000000; + stx_transfer.anchor_mode = TransactionAnchorMode::OnChainOnly; + stx_transfer.set_tx_fee(1); + stx_transfer.auth.set_origin_nonce(sender_nonce); + sender_nonce += 1; + + let mut tx_signer = StacksTransactionSigner::new(&stx_transfer); + tx_signer.sign_origin(&private_key).unwrap(); + let stx_transfer_signed = tx_signer.get_tx().unwrap(); + + stx_transfer_signed + }; + + let mut boot_tenures = vec![]; + for bitvec in bitvecs.iter() { + for has_tenure in bitvec { + if *has_tenure { + boot_tenures.push(NakamotoBootTenure::Sortition(vec![ + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + ])); + } else { + boot_tenures.push(NakamotoBootTenure::NoSortition(vec![ + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::TenureExtend(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::TenureExtend(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::TenureExtend(vec![next_stx_transfer()]), + NakamotoBootStep::Block(vec![next_stx_transfer()]), + NakamotoBootStep::TenureExtend(vec![next_stx_transfer()]), + ])); + } + } + } + + let plan = NakamotoBootPlan::new(test_name) + .with_private_key(private_key) + .with_pox_constants(rc_len, prepare_len) + .with_initial_balances(vec![(addr.into(), 1_000_000)]); + + let peer = plan.boot_into_nakamoto_peer(boot_tenures, Some(observer)); + peer +} + +fn check_inv_messages( + bitvecs: Vec>, + rc_len: u32, + nakamoto_start_burn_height: u64, + messages: Vec, +) { + for (msg_idx, msg) in messages.into_iter().enumerate() { + let StacksMessageType::NakamotoInv(inv) = msg else { + panic!("Did not receive an inv for reward cycle {}", msg_idx); + }; + for bit in 0..(inv.bitlen as usize) { + let burn_block_height = (msg_idx as u64) * u64::from(rc_len) + (bit as u64); + let msg_bit = inv.tenures[bit / 8] & (1 << (bit % 8)) != 0; + if burn_block_height < nakamoto_start_burn_height { + // inv doesn't cover epoch 2 + assert!( + !msg_bit, + "Bit {} in message {} is set but is before nakamoto-start height {} ({})", + bit, msg_idx, nakamoto_start_burn_height, burn_block_height + ); + continue; + } + + let inv_offset: u64 = burn_block_height - nakamoto_start_burn_height; + let bitvec_idx = (inv_offset / u64::from(rc_len)) as usize; + let expected_bit = if bitvec_idx >= bitvecs.len() { + false + } else { + bitvecs[bitvec_idx][(inv_offset % u64::from(rc_len)) as usize] + }; + assert_eq!(msg_bit, expected_bit, "Bit {} in message {} is {}, but expected {}. burn_block_height = {}, inv_offset = {}, bitvec_idx = {}, nakamoto_start_burn_height = {}", + bit, msg_idx, msg_bit, expected_bit, burn_block_height, inv_offset, bitvec_idx, nakamoto_start_burn_height); + } + } +} + +#[test] +fn test_nakamoto_invs_full() { + let observer = TestEventObserver::new(); + let bitvecs = vec![ + vec![true, true, true, true, true, true, true, true, true, true], + vec![true, true, true, true, true, true, true, true, true, true], + vec![true, true, true, true, true, true, true, true, true, true], + vec![true, true, true, true, true, true, true, true, true, true], + vec![true, true, true, true, true, true, true, true, true, true], + ]; + + let peer = make_nakamoto_peer_from_invs(function_name!(), &observer, 10, 3, bitvecs.clone()); + let (peer, reward_cycle_invs) = + peer_get_nakamoto_invs(peer, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + + let nakamoto_start = + NakamotoBootPlan::nakamoto_first_tenure_height(&peer.config.burnchain.pox_constants); + + eprintln!("{:#?}", &reward_cycle_invs); + assert_eq!(reward_cycle_invs.len(), 10); + check_inv_messages(bitvecs, 10, nakamoto_start, reward_cycle_invs); +} + +#[test] +fn test_nakamoto_invs_alternating() { + let observer = TestEventObserver::new(); + let bitvecs = vec![ + vec![ + true, false, true, false, true, false, true, true, true, true, + ], + vec![ + false, true, false, true, false, true, false, true, true, true, + ], + vec![ + true, false, true, false, true, false, true, true, true, true, + ], + vec![ + false, true, false, true, false, true, false, true, true, true, + ], + vec![ + true, false, true, false, true, false, true, true, true, true, + ], + ]; + + let peer = make_nakamoto_peer_from_invs(function_name!(), &observer, 10, 3, bitvecs.clone()); + let (peer, reward_cycle_invs) = + peer_get_nakamoto_invs(peer, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + + let nakamoto_start = + NakamotoBootPlan::nakamoto_first_tenure_height(&peer.config.burnchain.pox_constants); + + eprintln!("{:#?}", &reward_cycle_invs); + assert_eq!(reward_cycle_invs.len(), 10); + check_inv_messages(bitvecs, 10, nakamoto_start, reward_cycle_invs); +} + +#[test] +fn test_nakamoto_invs_sparse() { + let observer = TestEventObserver::new(); + let bitvecs = vec![ + vec![ + true, false, false, false, false, false, false, true, true, true, + ], + vec![ + false, true, false, false, false, false, false, true, true, true, + ], + vec![ + false, false, true, false, false, false, false, true, true, true, + ], + vec![ + false, false, false, true, false, false, false, true, true, true, + ], + vec![ + false, false, false, false, true, false, false, true, true, true, + ], + vec![ + false, false, false, false, false, true, false, true, true, true, + ], + vec![ + false, false, false, false, false, false, true, true, true, true, + ], + ]; + + let peer = make_nakamoto_peer_from_invs(function_name!(), &observer, 10, 3, bitvecs.clone()); + let (peer, reward_cycle_invs) = + peer_get_nakamoto_invs(peer, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + + let nakamoto_start = + NakamotoBootPlan::nakamoto_first_tenure_height(&peer.config.burnchain.pox_constants); + + eprintln!("{:#?}", &reward_cycle_invs); + assert_eq!(reward_cycle_invs.len(), 12); + check_inv_messages(bitvecs, 10, nakamoto_start, reward_cycle_invs); +} + +#[test] +fn test_nakamoto_invs_different_anchor_blocks() { + let observer = TestEventObserver::new(); + let bitvecs = vec![ + vec![true, true, true, true, true, true, false, true, true, true], + vec![true, true, true, true, true, false, false, true, true, true], + vec![ + true, true, true, true, false, false, false, true, true, true, + ], + vec![ + true, true, true, false, false, false, false, true, true, true, + ], + vec![ + true, true, false, false, false, false, false, true, true, true, + ], + vec![ + true, false, false, false, false, false, false, true, true, true, + ], + vec![ + false, false, false, false, false, false, false, true, true, true, + ], + ]; + + let peer = make_nakamoto_peer_from_invs(function_name!(), &observer, 10, 3, bitvecs.clone()); + let (peer, reward_cycle_invs) = + peer_get_nakamoto_invs(peer, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); + + let nakamoto_start = + NakamotoBootPlan::nakamoto_first_tenure_height(&peer.config.burnchain.pox_constants); + + eprintln!("{:#?}", &reward_cycle_invs); + assert_eq!(reward_cycle_invs.len(), 12); + check_inv_messages(bitvecs, 10, nakamoto_start, reward_cycle_invs); +}