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

[ETCM-1042] Switch to a case class implementation for blockchain branch #1073

Merged
merged 9 commits into from
Jul 30, 2021

Conversation

AurelienRichez
Copy link
Contributor

@AurelienRichez AurelienRichez commented Jul 22, 2021

Description

Instead of having a interface which tries to contains every method for a branch, we change it to be a simple case class. It will be more flexible for future changes ; e.g. implement some function for the canonical branch but not for other kinds of branch (or the other way around), without changing a central interface.

This PR

  • makes the blockchain implementation only contain the hash and the number of the tip (before it was the whole header, but it was not necessary)
  • uses a case class with a sealed trait, instead of a trait with methods to implement


/** An interface to manipulate blockchain branches */
trait Branch {
case class BestBranchSubset(tipBlockHash: ByteString, tipBlockNumber: BigInt) extends Branch
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did you add the suffix Subset here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It works with any subset on the best branch so I wanted to make it clear and kept the name in from my initial draft of the case class representation. The idea was that we could use it later to define a fork as a tip and a best branch subset which is common to the fork and our current best branch.

But I agree that it might be confusing and not really relevant to our use case for now. I'll just name it BestBranch as before

@AurelienRichez AurelienRichez force-pushed the refactor/ETCM-1042 branch 2 times, most recently from 15b4be9 to 7881d76 Compare July 28, 2021 08:24
}

/** Returns a block hash for the block at the given height if any */
def getHashByBlockNumber(branch: Branch, number: BigInt): Option[ByteString] = branch match {
Copy link
Contributor

Choose a reason for hiding this comment

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

what do you think of using a type alias for ByteString when we are talking about a hash?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would prefer a value class or a tagged type, because the type alias would just be "cosmetic". But it would probably be out of scope to introduce the new type in this PR.


/** An interface to manipulate blockchain branches */
trait Branch {
case class BestBranch(tipBlockHash: ByteString, tipBlockNumber: BigInt) extends Branch
Copy link
Contributor

Choose a reason for hiding this comment

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

I can imagine this case class being used for all the branches, not only the best one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not contain data specific to the best branch, yes. Right now it is more a marker type to tell the code that we can use the block number -> hash index.

@AurelienRichez AurelienRichez requested a review from jvdp July 29, 2021 07:00
@AurelienRichez AurelienRichez force-pushed the refactor/ETCM-1042 branch 2 times, most recently from 1691ef1 to 3a61349 Compare July 29, 2021 13:06
val number = getBestBlockNumber()
blockNumberMappingStorage
.get(number)
.orElse(blockNumberMappingStorage.get(appStateStorage.getBestBlockNumber()))
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this for? This is a work-around not related to this change, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it is not related to this change.
It's related to anastasiia's comment. It allows to be consistent with getBestBlock which fallbacks to what is in the storage if the block from memory does not exists.

case BestBranch(_, tipBlockNumber) =>
(for {
header <- getBlockHeaderByHash(hash) if header.number <= tipBlockNumber
hash <- getHashByBlockNumber(branch, header.number)
Copy link
Contributor

@jvdp jvdp Jul 29, 2021

Choose a reason for hiding this comment

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

You are shadowing hash here; perhaps you can specify the names a bit more?

Also, this relies on using the "hash by block number" index as an authority of "this chain". How do you envision that to work in the new architecture? Will this index be kept up to date eg. when reorganising?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Fixed

For now we have to keep the index updated indeed. Right now isInChain is used by BranchResolution.resolveBranch to check if a sequence of blocks has a parent inside the canonical chain (i.e. if the sequence is rooted). In the end, we would like for mantis to be able to synchronize without maintaining that index. It would still be there to help json rpc calls, but mantis would not have to wait for it to be rebuilt to continue its work.

That said, in the new design, we won't really care that a parent is part of the chain, but we will care that a block is rooted so that we can compare its weight to the current best block.

If we want to do that, I think it depend on how we store incoming blocks:

  • if only rooted block are stored (i.e. we store block once they form a branch), then if it is in storage, all is good and we can compare it with the best branch.
  • if we store every block it gets trickier. I think in that case we will have to add some block metadata which tells us if a block is rooted or not so that we can easily tell "the parent is rooted so this block is also rooted", probably with different types of blocks.

/** Checks if given block hash is in this chain. (i.e. is an ancestor of the tip block) */
def isInChain(hash: ByteString): Boolean
}
case object EmptyBranch extends Branch
Copy link
Contributor

Choose a reason for hiding this comment

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

So we'll have multiple types of branch later on, right? For the purposes of the code you're changing now (branch as produced by getBestBranch) you could also name the trait BestBranch and rename empty branch to Genesis, which resolves to a block number of 1. (Or is it 0?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we will have several types yes (well, best branch and "other" branch). I don't really know what to do with the EmptyBranch as I return it when even the genesis is not found (normally that can happen only during tests). I wonder if we should not add the genesis as a parameter to the block storage, to ensure that it is always there.

@AurelienRichez AurelienRichez merged commit b9f3c0b into develop Jul 30, 2021
@AurelienRichez AurelienRichez deleted the refactor/ETCM-1042 branch July 30, 2021 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants