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

Kauri module implementation #89

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Kauri module implementation #89

wants to merge 2 commits into from

Conversation

hanish520
Copy link
Contributor

kauri implementation first draft

@codecov-commenter
Copy link

codecov-commenter commented Feb 21, 2023

Codecov Report

Merging #89 (e7a5cfc) into master (5196107) will increase coverage by 1.46%.
The diff coverage is 81.40%.

📣 This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

@@            Coverage Diff             @@
##           master      #89      +/-   ##
==========================================
+ Coverage   62.61%   64.07%   +1.46%     
==========================================
  Files          73       75       +2     
  Lines        7185     7525     +340     
==========================================
+ Hits         4499     4822     +323     
- Misses       2382     2398      +16     
- Partials      304      305       +1     
Flag Coverage Δ
unittests 64.07% <81.40%> (+1.46%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
cmd/hotstuff/main.go 0.00% <ø> (ø)
cmd/plot/main.go 0.00% <ø> (ø)
consensus/byzantine/byzantine.go 1.78% <0.00%> (-0.14%) ⬇️
hotstuff.go 100.00% <ø> (ø)
internal/cli/root.go 24.19% <ø> (ø)
internal/orchestration/worker.go 65.42% <ø> (+0.25%) ⬆️
internal/proto/hotstuffpb/convert.go 98.08% <ø> (ø)
leaderrotation/carousel.go 3.77% <ø> (ø)
metrics/types/event.go 100.00% <ø> (ø)
modules/modules.go 100.00% <ø> (ø)
... and 20 more

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

impl.srv.logger.Infof("Failed to get client ID: %v", err)
return
}
proposal.Block.Proposer = uint32(id)
Copy link
Member

Choose a reason for hiding this comment

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

Given that proposal isn't used later in this method, will this have an effect?
Or is this one still necessary: proposeMsg := hotstuffpb.ProposalFromProto(proposal)?

Copy link
Member

Choose a reason for hiding this comment

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

It could perhaps be helpful to add a comment to explain why Kauri can use the ID from proposal.Block.Proposer while non-Kauri gets the ID from context.


cs.configuration.Propose(proposal)
if cs.kauri == nil {
cs.configuration.Propose(proposal)
Copy link
Member

Choose a reason for hiding this comment

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

Would it be useful to have a comment explaining that Kauri won't propose here, but elsewhere?

uint32 ID = 1;
hotstuffpb.QuorumSignature Signature = 2;
uint64 View = 3;
}
Copy link
Member

Choose a reason for hiding this comment

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

newline

@@ -0,0 +1,320 @@
// Package kauri contains the implementation of the kauri protocol
Copy link
Member

Choose a reason for hiding this comment

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

... the Kauri protocol.

modules.RegisterModule("kauri", New)
}

// TreeType determines the type of tree to be built, for future extension
Copy link
Member

Choose a reason for hiding this comment

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

... to be built (for future extension).

Copy link
Member

Choose a reason for hiding this comment

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

Is this supposed to be exported?

// TreeType determines the type of tree to be built, for future extension
type TreeType int

// NoFaultTree is a tree without any faulty replicas in the tree, which is currently supported.
Copy link
Member

Choose a reason for hiding this comment

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

These comments should be moved inside the const block above each of the constants so that the docs are generated correctly.

}
k.tree = CreateTree(k.configuration.Len(), k.opts.ID())

// pIDs := make(map[hotstuff.ID]int)
Copy link
Member

Choose a reason for hiding this comment

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

Still need this commented code?

}

// CreateTree Creates the tree configuration, currently only fault free tree configuration is supported.
func CreateTree(configurationLength int, myID hotstuff.ID) TreeConfiguration {
Copy link
Member

Choose a reason for hiding this comment

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

This is the interface anti-pattern in Go that I mentioned a while back. Generally, we avoid returning an interface type and defining interfaces before they are needed.

Suggest to rename this function to newFaultFreeTree and return *faultFreeTree instead of the interface. If you later add a new tree type, you can create another construction function that returns the other tree type.

}

// FaultFreeTree implements a fault free tree configuration.
type FaultFreeTree struct {
Copy link
Member

@meling meling Mar 14, 2023

Choose a reason for hiding this comment

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

Consider whether or not all these types and methods and such needs to be exported... I haven't checked all methods etc, but looks like most of methods and types are internal to the kauri package and should not be exported unless you think there is a need for managing trees outside kauri.

return nil
}
temp := configurationLength
temp = temp - 1 //root
Copy link
Member

Choose a reason for hiding this comment

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

temp := configurationLength - 1 // root

Avoids Line 36.

temp := configurationLength
temp = temp - 1 //root
height := 1
for i := 1; temp > 0; i++ {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this could be converted to a helper function:

func height(maxChild float64, treeNodes int) int {
	remainingNodes := treeNodes - 1 // root
	height := 1
	for i := 1; remainingNodes > 0; i++ {
		remainingNodes -= int(math.Pow(maxChild, float64(i)))
		height++
	}
	return height
}

And you could add this test:

func TestTreeHeight(t *testing.T) {
	const MaxChild = 2
	tests := []struct {
		configurationLength int
		expectedHeight      int
	}{
		{1, 1},
		{2, 2},
		{3, 2},
		{4, 3},
		{5, 3},
		{6, 3},
		{7, 3},
		{8, 4},
		{9, 4},
		{10, 4},
		{11, 4},
		{12, 4},
		{13, 4},
		{14, 4},
		{15, 4},
		{16, 5},
		{17, 5},
	}
	for _, test := range tests {
		actualHeight := height(MaxChild, test.configurationLength)
		if actualHeight != test.expectedHeight {
			t.Errorf("height(%d) = %d, expected %d", test.configurationLength, actualHeight, test.expectedHeight)
		}
	}
}

posToIDMapping map[int]hotstuff.ID
}

// CreateTree Creates the tree configuration, currently only fault free tree configuration is supported.
Copy link
Member

Choose a reason for hiding this comment

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

Renaming this function to newFaultFreeTree this doc comment can be replaced with:
newFaultFreeTree creates a fault free tree configuration.

There is no need to say that it is the only supported tree type.

return t.GetChildrenOfNode(t.ID)
}

func (t *FaultFreeTree) isWithInIndex(position int) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a better name for this? Could this be simply contains(index int) bool?

}
}

// GetParent fetches the ID of the parent, if root, returns itself.
Copy link
Member

Choose a reason for hiding this comment

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

In Go we typically don't use Get for getters... You can simply call this Parent().

Also, the doc comment should maybe say something like:

Parent returns the tree node's parent ID and true.
Otherwise, if the tree node is the root, it returns its own ID and false.

seed := k.opts.SharedRandomSeed() + int64(binary.LittleEndian.Uint64(hash[:]))
//Shuffle the list of IDs using the shared random seed + the first 8 bytes of the hash.
rnd := rand.New(rand.NewSource(seed))
rnd.Shuffle(len(ids), reflect.Swapper(ids))
Copy link
Member

Choose a reason for hiding this comment

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

Should avoid reflect here. I think something like this will work:

func(i, j int) { ids[i], ids[j] = ids[j], ids[i] })

func (k *Kauri) SendContributionToParent() {
parent, ok := k.tree.GetParent()
if ok {
node, isPresent := k.nodes[parent]
Copy link
Member

Choose a reason for hiding this comment

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

Consider moving the nodes map to the tree and let tree.Parent() return the parent node directly instead of the hotstuff.ID.

return true
}

func (k *Kauri) randomizeIDS(hash hotstuff.Hash, leaderID hotstuff.ID) map[hotstuff.ID]int {
Copy link
Member

Choose a reason for hiding this comment

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

Suggest to rename this to shuffleTreeNodes and move it into the tree. And instead of passing in the hash you could move the seed calculation out from this method... so that the signature becomes:

func (t *faultFreeTree) shuffleTreeNodes(seed int64, leaderID hotstuff.ID)

This way you can update the internals of the tree directly avoiding the passing around of maps from this method to InitializeWithPIDs (which could be removed; its functionality can be done in the constructor and in the above method.)


func (k *Kauri) initializeConfiguration() {
kauriCfg := kauripb.ConfigurationFromRaw(k.configuration.GetRawConfiguration(), nil)
for _, n := range kauriCfg.Nodes() {
Copy link
Member

Choose a reason for hiding this comment

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

Seems better to move this into the tree constructor like this:

func newFaultFreeTree(cfg kauripb.Configuration, myID hotstuff.ID) *FaultFreeTree {
	nodes := make(map[hotstuff.ID]*kauripb.Node)
	idMappings := make(map[hotstuff.ID]int)
	posToIDMapping := make(map[int]hotstuff.ID)
	for i, n := range cfg.Nodes() {
		nodes[hotstuff.ID(n.ID())] = n
		idMappings[hotstuff.ID(i+1)] = i
		posToIDMapping[i] = hotstuff.ID(i + 1)
	}
	// some code removed for brevity
	return &FaultFreeTree{
		ID:                  myID,
		ConfigurationLength: len(nodes),
		height:              height,
		nodes:               nodes,
		idToPosMapping:      idMappings,
		posToIDMapping:      posToIDMapping,
	}
}

Note: I don't think you actually need all three maps, but I didn't check.
Also, it seems the hotstuff.ID generation in the above code could be using only a single one like this:

	id := hotstuff.ID(n.ID())
	nodes[id] = n
	idMappings[id] = i
	posToIDMapping[i] = id

Copy link
Member

Choose a reason for hiding this comment

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

This said, I think a more idiomatic design would be to pass the TreeConfiguration as an argument to the Karui module. But I don't know how to do that with the module system.

// pIDs[id] = index
// index++
// }
idMappings := make(map[hotstuff.ID]int)
Copy link
Member

Choose a reason for hiding this comment

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

I believe this code is redundant with the suggested constructor code above.

k.blockHash = pc.BlockHash()
k.currentView = p.Block.View()
k.aggregatedContribution = pc.Signature()
ids := k.randomizeIDS(k.blockHash, k.leaderRotation.GetLeader(k.currentView))
Copy link
Member

Choose a reason for hiding this comment

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

This can be moved to the tree and merged with InitializeWithPIDs() as outlined in the comment below.

// TreeConfiguration is an abstraction for a tree communication model.
type TreeConfiguration interface {
InitializeWithPIDs(ids map[hotstuff.ID]int)
GetHeight() int
Copy link
Member

Choose a reason for hiding this comment

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

We avoid Get in Go.


// FaultFreeTree implements a fault free tree configuration.
type FaultFreeTree struct {
ID hotstuff.ID
Copy link
Member

Choose a reason for hiding this comment

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

If I understand correctly this ID field is the current/local tree node. If so, perhaps we should use a more clear name.

Copy link
Member

Choose a reason for hiding this comment

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

Also, maybe it shouldn't be exported.

if !ok {
return parent, false
}
parentPos := t.idToPosMapping[parent]
Copy link
Member

Choose a reason for hiding this comment

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

parentPos := t.idToPosMapping[parent] - 1

would avoid the two parentPos-1 on Lines 84 and 87.

@@ -109,6 +109,7 @@ func New(conf Config, builder modules.Builder) (replica *Replica) {
srv.clientSrv.cmdCache,
srv.clientSrv.cmdCache,
)
//builder.Options().SetShouldVerifyVotesSync()
Copy link
Member

Choose a reason for hiding this comment

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

Should this be commented as is, or enabled or removed?

Copy link
Member

@meling meling left a comment

Choose a reason for hiding this comment

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

I didn't comment on all methods in FaultFreeTree and Kauri, but several of the methods are non-trivial to review. That is, there should be tests for these methods that operate with different indexes and boundaries that are easy to get wrong.

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.

3 participants