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

Use squares with a width that is a power of two #331

Merged
merged 5 commits into from
May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 5 additions & 28 deletions p2p/ipld/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package ipld

import (
"context"
"errors"
"fmt"
"math"
"math/rand"
Expand Down Expand Up @@ -246,8 +245,8 @@ func GetLeafData(

func leafPath(index, total uint32) ([]string, error) {
// ensure that the total is a power of two
if total != nextPowerOf2(total) {
return nil, errors.New("expected total to be a power of 2")
if !isPowerOf2(total) {
return nil, fmt.Errorf("expected total to be a power of 2, got %d", total)
}

if total == 0 {
Expand All @@ -269,29 +268,7 @@ func leafPath(index, total uint32) ([]string, error) {
return path, nil
}

// nextPowerOf2 returns the next lowest power of 2 unless the input is a power
// of two, in which case it returns the input
func nextPowerOf2(v uint32) uint32 {
if v == 1 {
return 1
}
// keep track of the input
i := v

// find the next highest power using bit mashing
v--
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v++

// check if the input was the next highest power
if i == v {
return v
}

// return the next lowest power
return v / 2
// isPowerOf2 returns checks if a given number is a power of two
func isPowerOf2(v uint32) bool {
return math.Ceil(math.Log2(float64(v))) == math.Floor(math.Log2(float64(v)))
Copy link
Member

Choose a reason for hiding this comment

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

Neat! I'm sure there is a way to do this without floats but let's think about this later. The above is certainly clearer than before.

}
25 changes: 15 additions & 10 deletions p2p/ipld/read_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,36 +58,41 @@ func TestLeafPath(t *testing.T) {
}
}

func TestNextPowerOf2(t *testing.T) {
func Test_isPowerOf2(t *testing.T) {
type test struct {
input uint32
expected uint32
expected bool
}
tests := []test{
{
input: 2,
expected: 2,
expected: true,
},
{
input: 11,
expected: 8,
expected: false,
},
{
input: 511,
expected: 256,
expected: false,
},

{
input: 0,
expected: true,
},
{
input: 1,
expected: 1,
expected: true,
},
{
input: 0,
expected: 0,
input: 16,
expected: true,
},
}
for _, tt := range tests {
res := nextPowerOf2(tt.input)
assert.Equal(t, tt.expected, res)
res := isPowerOf2(tt.input)
assert.Equal(t, tt.expected, res, fmt.Sprintf("input was %d", tt.input))
}
}

Expand Down
36 changes: 29 additions & 7 deletions types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -1381,9 +1381,9 @@ func (data *Data) ComputeShares() (NamespacedShares, int) {
msgShares := data.Messages.splitIntoShares()
curLen := len(txShares) + len(intermRootsShares) + len(evidenceShares) + len(msgShares)

// FIXME(ismail): this is not a power of two
// see: https://github.com/lazyledger/lazyledger-specs/issues/80 and
wantLen := getNextSquareNum(curLen)
// find the number of shares needed to create a square that has a power of
// two width
wantLen := paddedLen(curLen)

// ensure that the min square size is used
if wantLen < minSharecount {
Expand All @@ -1400,10 +1400,32 @@ func (data *Data) ComputeShares() (NamespacedShares, int) {
tailShares...), curLen
}

func getNextSquareNum(length int) int {
width := int(math.Ceil(math.Sqrt(float64(length))))
// TODO(ismail): make width a power of two instead
return width * width
// paddedLen calculates the number of shares needed to make a power of 2 square
// given the current number of shares
func paddedLen(length int) int {
width := uint32(math.Ceil(math.Sqrt(float64(length))))
width = nextHighestPowerOf2(width)
return int(width * width)
}

// nextPowerOf2 returns the next highest power of 2 unless the input is a power
// of two, in which case it returns the input
func nextHighestPowerOf2(v uint32) uint32 {
if v == 0 {
return 0
}

// find the next highest power using bit mashing
v--
Copy link
Member

Choose a reason for hiding this comment

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

This looks like it might not behave properly if v == 0.

Copy link
Member Author

Choose a reason for hiding this comment

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

https://github.com/lazyledger/lazyledger-core/blob/e5a7683c44bbf92d759b127226c0652c7b9018e5/types/block_test.go#L1420

Currently input of 0 results in 0 out, should we be expecting an output of 2?

Copy link
Member

@adlerjohn adlerjohn May 14, 2021

Choose a reason for hiding this comment

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

Ah you're right, it does output 0. That seems fine , since you later check

https://github.com/lazyledger/lazyledger-core/blob/40510a70e92d075ab77b2a13b87d1d53743f684b/types/block.go#L1388

It would still be nice to maybe have an input of 0 return 0 as a special case just for clarity.

Copy link
Member Author

Choose a reason for hiding this comment

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

added the check and renamed to nextHighestPowerOfTwo to be clearer 4aa3372

v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v++

// return the next highest power
return v
}

type Message struct {
Expand Down
64 changes: 64 additions & 0 deletions types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1398,6 +1398,70 @@ func TestPutBlock(t *testing.T) {
}
}

func TestPaddedLength(t *testing.T) {
type test struct {
input, expected int
}
tests := []test{
{0, 0},
{1, 1},
{2, 4},
{4, 4},
{5, 16},
{11, 16},
{128, 256},
}
for _, tt := range tests {
res := paddedLen(tt.input)
assert.Equal(t, tt.expected, res)
}
}

func TestNextHighestPowerOf2(t *testing.T) {
type test struct {
input uint32
expected uint32
}
tests := []test{
{
input: 2,
expected: 2,
},
{
input: 11,
expected: 16,
},
{
input: 511,
expected: 512,
},
{
input: 1,
expected: 1,
},
{
input: 0,
expected: 0,
},
{
input: 5,
expected: 8,
},
{
input: 6,
expected: 8,
},
{
input: 16,
expected: 16,
},
}
for _, tt := range tests {
res := nextHighestPowerOf2(tt.input)
assert.Equal(t, tt.expected, res)
}
}

func generateRandomMsgOnlyData(msgCount int) Data {
out := make([]Message, msgCount)
for i, msg := range generateRandNamespacedRawData(msgCount, NamespaceSize, MsgShareSize-2) {
Expand Down