Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cuprated: Blockchain Manager #274

Merged
merged 52 commits into from
Oct 8, 2024
Merged

cuprated: Blockchain Manager #274

merged 52 commits into from
Oct 8, 2024

Conversation

Boog900
Copy link
Member

@Boog900 Boog900 commented Sep 9, 2024

What

This PR contains the blockchain manager, there are still quite a few TODOs however I think it is in a state to be reviewed and merged.

More details in comments

@github-actions github-actions bot added A-p2p Related to P2P. A-test-utils Related to test-utils. A-dependency Related to dependencies, or changes to a Cargo.{toml,lock} file. labels Sep 9, 2024
@github-actions github-actions bot removed A-test-utils Related to test-utils. A-storage Related to storage. A-types Related to types. labels Oct 3, 2024
@github-actions github-actions bot removed the A-helper Related to cuprate-helper. label Oct 3, 2024
Copy link
Member Author

@Boog900 Boog900 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of this code still needs a bit of work, however I am starting to need to build other sections so to prevent a massive PR for the whole binary this will have to do.

Comment on lines 1 to 4
//! The blockchain manger interface.
//!
//! This module contains all the functions to mutate the blockchain's state in any way, through the
//! blockchain manger.
Copy link
Member Author

@Boog900 Boog900 Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains the interface for the blockchain manager.

I decided to not go with a tower::service interface, as I just don't think it is needed here. The benefits of a Service interface here would just be uniformity with every other service, the drawbacks are the extra over head of holding/cloning another handle (admittedly low) and just the extra hassle of holding another service handle.

I think these free functions as a way to mutate the blockchain makes more sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you say this about every Service though? Breaking the pattern and using global statics seems lazy? This is yet another interface that must be learned and it doesn't come with docs on how to use it.

If it's a 1-off in cuprated I think it's fine but there should be documentation on what the statics are, how to use generally use the interface, etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, the API here should be pretty easy to use - all you need to do is call the functions, I'll add some more docs.

Couldn't you say this about every Service though

Other service are exposed this one is not. There are other benefits to the Service interface, like the wide range of middleware but since we are not going to make use of them for the blockchain manager that isn't a benefit here.

binaries/cuprated/src/blockchain.rs Show resolved Hide resolved
binaries/cuprated/src/blockchain/interface.rs Show resolved Hide resolved
binaries/cuprated/src/blockchain/manager.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/manager.rs Show resolved Hide resolved
binaries/cuprated/src/blockchain/manager/handler.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/syncer.rs Show resolved Hide resolved
@Boog900 Boog900 marked this pull request as ready for review October 3, 2024 22:16
@Boog900 Boog900 requested a review from hinto-janai October 3, 2024 22:16
Copy link
Contributor

@hinto-janai hinto-janai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only nits, left another review.

binaries/cuprated/src/blockchain/interface.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/interface.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/interface.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/manager.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/manager/commands.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/manager/handler.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/syncer.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/syncer.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/types.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/blockchain/types.rs Outdated Show resolved Hide resolved
Comment on lines +54 to +75
blockchain_write_handle
.ready()
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR)
.call(BlockchainWriteRequest::WriteBlock(
VerifiedBlockInformation {
block_blob: genesis.serialize(),
txs: vec![],
block_hash: genesis.hash(),
pow_hash: cryptonight_hash_v0(&genesis.serialize_pow_hash()),
height: 0,
generated_coins: genesis.miner_transaction.prefix().outputs[0]
.amount
.unwrap(),
weight: genesis.miner_transaction.weight(),
long_term_weight: genesis.miner_transaction.weight(),
cumulative_difficulty: 1,
block: genesis,
},
))
.await
.expect(PANIC_CRITICAL_SERVICE_ERROR);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to change for now but I still think cuprate_constants::genesis should exist - having the genesis block inlined in some async function in cuprated feels wrong. It's not about de-duplicatation but more about separating concerns and being clean, these constants deserve a name instead of being inlined.

Also, this is only mainnet right?

Copy link
Member Author

@Boog900 Boog900 Oct 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having the genesis block inlined in some async function in cuprated feels wrong

This is just mapping the genesis block got from generate_genesis_block to VerifiedBlockInformation.

Also, this is only mainnet right?

No this should work on all 3 current networks, although it does (like monerod) make some assumptions about the genesis blocks format

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapping

I realize this, I'm saying this mapping should exist in cuprate_constants::genesis as well, next to the Block.

Comment on lines 1 to 4
//! The blockchain manger interface.
//!
//! This module contains all the functions to mutate the blockchain's state in any way, through the
//! blockchain manger.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't you say this about every Service though? Breaking the pattern and using global statics seems lazy? This is yet another interface that must be learned and it doesn't come with docs on how to use it.

If it's a 1-off in cuprated I think it's fine but there should be documentation on what the statics are, how to use generally use the interface, etc.

};

/// The channel used to send [`BlockchainManagerCommand`]s to the blockchain manger.
pub static COMMAND_TX: OnceLock<mpsc::Sender<BlockchainManagerCommand>> = OnceLock::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a pub static OnceLock read/write available to the entire binary doesn't seem necessary, there should also be docs on who/when/where this OnceLock should be initialized.

Can this be privately defined where it is initiated then accompanied with:

/// Access to the blockchain manager's [...]
///
/// # Invariant
/// Must be initialized with [...]
///
/// # Panics
/// Panics if not initialized in [...]
pub fn command_tx() -> &mpsc::Sender<BlockchainManagerCommand> {
    &*COMMAND_TX.get().expect("wasn't initialized")
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops yeah wasn't meant to be fully public

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some more docs to this

/// This is used over something like a dashmap as we expect a lot of collisions in a short amount of
/// time for new blocks so we would lose the benefit of sharded locks. A dashmap is made up of `RwLocks`
/// which are also more expensive than `Mutex`s.
pub static BLOCKS_BEING_HANDLED: OnceLock<Mutex<HashSet<[u8; 32]>>> = OnceLock::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be pub? It's only currently used in this file.

To be honest pub static Mutex is pretty bad code smell - global resources like this really should be documented with who/when/how it's used.

If this only needs to be mutated within this file, what about doing the same as COMMAND_TX? Make this private and expose a fn() -> &HashSet<_, _> while documented that it takes a lock, and the usage patterns of that lock.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved it into the only function it is needed in + made it a LazyLock

Comment on lines +89 to +95
// TODO: remove this when we have a working tx-pool.
if given_txs.len() != block.transactions.len() {
return Err(IncomingBlockError::UnknownTransactions(
block_hash,
(0..usize_to_u64(block.transactions.len())).collect(),
));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why usize_to_u64? Shouldn't it be usize if it's an index?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values are sent over the P2P network, which requires a u64. I could change this and probably will when the tx-pool is made and do the conversion in the P2P hanlder.

However I would rather wait to make the tx-pool rather than preemptively change this.

+ 'static,
CN::Future: Send + 'static,
{
tracing::info!("Starting blockchain syncer");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use tracing::{info,debug,trace} here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine - this file will be completely changed at some point as well


impl Service<ChainSvcRequest> for ChainService {
type Response = ChainSvcResponse;
type Error = tower::BoxError;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyhow::Error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to match the bounds in the p2p crate, I am planning to change those in a separate PR though

binaries/cuprated/src/blockchain/types.rs Outdated Show resolved Hide resolved
binaries/cuprated/src/constants.rs Show resolved Hide resolved
///
/// A [`RwLock`] where a write lock is taken during a reorg and a read lock can be taken
/// for any operation which must complete without a reorg happening.
pub static REORG_LOCK: RwLock<()> = RwLock::const_new(());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mixed feelings using locks in a C-style like this... The solution is to design things such that you can scope and pass resources properly, I'm not going to ask this to be changed since that will involve a lot of work but I hope you realize this pattern is bad as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related issue: #305

@Boog900 Boog900 requested a review from hinto-janai October 5, 2024 20:32
pub static BLOCKS_BEING_HANDLED: OnceLock<Mutex<HashSet<[u8; 32]>>> = OnceLock::new();
/// This channel is initialized in [`init_blockchain_manager`](super::manager::init_blockchain_manager), the functions
/// in this file document what happens if this is not initialized when they are called.
pub(super) static COMMAND_TX: OnceLock<mpsc::Sender<BlockchainManagerCommand>> = OnceLock::new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (super) is fine but I would have preferred if it were moved to manager.rs and kept private - it is still writeable from interface.rs since it's defined there, even though it only needs a &mpsc::Sender.

Doesn't matter too much I guess but then again things being scoped is nice.

Comment on lines +73 to +74
static BLOCKS_BEING_HANDLED: LazyLock<Mutex<HashSet<[u8; 32]>>> =
LazyLock::new(|| Mutex::new(HashSet::new()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could/should this be a tokio::sync::Mutex?

As it is now, if handle_incoming_block is asynchronously called from many many places, the std::sync::Mutex may block the executor more than tokio::sync::Mutex, right?

Although IDK really, the lock isn't held for long so this probably doesn't matter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although std Mutex is blocking we hold the lock for a tiny amount of time + an async Mutex is a lot more expensive: https://docs.rs/tokio/latest/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use

Comment on lines 49 to 61
/// Broadcast a valid block to the network.
async fn broadcast_block(&mut self, block_bytes: Bytes, blockchain_height: usize) {
self.broadcast_svc
.ready()
.await
.expect("Broadcast service cannot error.")
.call(BroadcastRequest::Block {
block_bytes,
current_blockchain_height: usize_to_u64(blockchain_height),
})
.await
.expect("Broadcast service cannot error.");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. Maybe Broadcast service is Infallible would work then, current msg is fine too.

@Boog900 Boog900 merged commit 8be3698 into main Oct 8, 2024
6 checks passed
@Boog900 Boog900 deleted the cuprated-blockchain branch October 8, 2024 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-binaries Related to binaries. A-consensus Related to consensus. A-dependency Related to dependencies, or changes to a Cargo.{toml,lock} file. A-p2p Related to P2P. A-workspace Changes to a root workspace file or general repo file.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants