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

Implement spec-compliant share splitting #246

Merged
merged 31 commits into from
Mar 25, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2137d67
Export block data compute shares.
adlerjohn Mar 22, 2021
be307d5
Add doc on exported func.
adlerjohn Mar 22, 2021
e2ba998
Refactor to use ShareSize constant directly.
adlerjohn Mar 22, 2021
b16f200
Change message splitting to prefix namespace ID.
adlerjohn Mar 22, 2021
7d23abf
Get ready for chunking contiguous.
adlerjohn Mar 23, 2021
2331a72
Add start index comp.
adlerjohn Mar 23, 2021
665b611
Implement chunking for contiguous.
adlerjohn Mar 23, 2021
5a0c5ea
Make tests print newline.
adlerjohn Mar 23, 2021
95f2fb7
Remove superfluous init.
adlerjohn Mar 23, 2021
2a68a84
Update types/block.go
adlerjohn Mar 23, 2021
c956a63
Fix to reserve, not allocate.
adlerjohn Mar 23, 2021
8d75195
Merge branch 'adlerjohn-share_arrangement_fix' of github.com:lazyledg…
adlerjohn Mar 23, 2021
fe2c646
Clean up.
adlerjohn Mar 23, 2021
0a47c65
Add termination condition.
adlerjohn Mar 23, 2021
488ae0e
Fix continue.
adlerjohn Mar 23, 2021
e0c0940
Rename append contiguous to split contiguous.
adlerjohn Mar 23, 2021
8e2e36d
Fix bytes to fetch computation.
adlerjohn Mar 23, 2021
99fb0e2
Fix start index computation.
adlerjohn Mar 23, 2021
d00a9bc
Update test for small tx.
adlerjohn Mar 23, 2021
132523a
Fix test for message.
adlerjohn Mar 23, 2021
d9adcfd
Clean up.
adlerjohn Mar 23, 2021
b6c6e30
Fix evidence test.
adlerjohn Mar 23, 2021
d76c838
Fix large tx test.
adlerjohn Mar 23, 2021
8f25a9b
Fix lint.
adlerjohn Mar 23, 2021
defcd50
Add test for two contiguous.
adlerjohn Mar 24, 2021
25f5d94
Fix start index computation.
adlerjohn Mar 24, 2021
92301ea
Update comments.
adlerjohn Mar 24, 2021
b70b103
Make tx and msg adjusted share sizes exported constants.
adlerjohn Mar 25, 2021
ff636c4
Panic on hopefully-unreachable condition instead of silently skipping.
adlerjohn Mar 25, 2021
0a731c9
Fix lint.
adlerjohn Mar 25, 2021
3eb7223
Update hardcoded response for block format.
adlerjohn Mar 25, 2021
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
31 changes: 18 additions & 13 deletions types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (b *Block) fillHeader() {
// fillDataAvailabilityHeader fills in any remaining DataAvailabilityHeader fields
// that are a function of the block data.
func (b *Block) fillDataAvailabilityHeader() {
namespacedShares := b.Data.computeShares()
namespacedShares := b.Data.ComputeShares()
shares := namespacedShares.RawShares()
if len(shares) == 0 {
// no shares -> no row/colum roots -> hash(empty)
Expand Down Expand Up @@ -272,7 +272,7 @@ func (b *Block) PutBlock(ctx context.Context, nodeAdder format.NodeAdder) error
}

// recompute the shares
namespacedShares := b.Data.computeShares()
namespacedShares := b.Data.ComputeShares()
shares := namespacedShares.RawShares()

// don't do anything if there is no data to put on IPFS
Expand Down Expand Up @@ -1312,41 +1312,44 @@ type IntermediateStateRoots struct {
RawRootsList []tmbytes.HexBytes `json:"intermediate_roots"`
}

func (roots IntermediateStateRoots) splitIntoShares(shareSize int) NamespacedShares {
func (roots IntermediateStateRoots) splitIntoShares() NamespacedShares {
shares := make([]NamespacedShare, 0)
rawDatas := make([][]byte, 0, len(roots.RawRootsList))
for _, root := range roots.RawRootsList {
rawData, err := root.MarshalDelimited()
if err != nil {
panic(fmt.Sprintf("app returned intermediate state root that can not be encoded %#v", root))
}
shares = appendToShares(shares, IntermediateStateRootsNamespaceID, rawData, shareSize)
rawDatas = append(rawDatas, rawData)
}
shares = appendToSharesContiguous(shares, IntermediateStateRootsNamespaceID, rawDatas)
return shares
}

func (msgs Messages) splitIntoShares(shareSize int) NamespacedShares {
func (msgs Messages) splitIntoShares() NamespacedShares {
shares := make([]NamespacedShare, 0)
for _, m := range msgs.MessagesList {
rawData, err := m.MarshalDelimited()
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
panic(fmt.Sprintf("app accepted a Message that can not be encoded %#v", m))
}
shares = appendToShares(shares, m.NamespaceID, rawData, shareSize)
shares = appendToShares(shares, m.NamespaceID, rawData)
}
return shares
}

func (data *Data) computeShares() NamespacedShares {
// ComputeShares splits block data into shares of an original data square.
func (data *Data) ComputeShares() NamespacedShares {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
// TODO(ismail): splitting into shares should depend on the block size and layout
// see: https://github.com/lazyledger/lazyledger-specs/blob/master/specs/block_proposer.md#laying-out-transactions-and-messages

// reserved shares:
txShares := data.Txs.splitIntoShares(ShareSize)
intermRootsShares := data.IntermediateStateRoots.splitIntoShares(ShareSize)
evidenceShares := data.Evidence.splitIntoShares(ShareSize)
txShares := data.Txs.splitIntoShares()
intermRootsShares := data.IntermediateStateRoots.splitIntoShares()
evidenceShares := data.Evidence.splitIntoShares()

// application data shares from messages:
msgShares := data.Messages.splitIntoShares(ShareSize)
msgShares := data.Messages.splitIntoShares()
curLen := len(txShares) + len(intermRootsShares) + len(evidenceShares) + len(msgShares)

// FIXME(ismail): this is not a power of two
Expand Down Expand Up @@ -1587,8 +1590,9 @@ func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceList) error {
return nil
}

func (data *EvidenceData) splitIntoShares(shareSize int) NamespacedShares {
func (data *EvidenceData) splitIntoShares() NamespacedShares {
shares := make([]NamespacedShare, 0)
rawDatas := make([][]byte, 0, len(data.Evidence))
for _, ev := range data.Evidence {
var rawData []byte
var err error
Expand All @@ -1608,8 +1612,9 @@ func (data *EvidenceData) splitIntoShares(shareSize int) NamespacedShares {
if err != nil {
panic(fmt.Sprintf("evidence included in evidence pool that can not be encoded %#v, err: %v", ev, err))
}
shares = appendToShares(shares, EvidenceNamespaceID, rawData, shareSize)
rawDatas = append(rawDatas, rawData)
}
shares = appendToSharesContiguous(shares, EvidenceNamespaceID, rawDatas)
return shares
}

Expand Down
2 changes: 1 addition & 1 deletion types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,7 @@ func TestPutBlock(t *testing.T) {
defer cancel()

block.fillDataAvailabilityHeader()
tc.blockData.computeShares()
tc.blockData.ComputeShares()
for _, rowRoot := range block.DataAvailabilityHeader.RowsRoots.Bytes() {
// recreate the cids using only the computed roots
cid, err := nodes.CidFromNamespacedSha256(rowRoot)
Expand Down
3 changes: 3 additions & 0 deletions types/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const (
// NamespaceSize is the namespace size in bytes.
NamespaceSize = 8

// ShareReservedBytes is the reserved bytes for contiguous appends.
ShareReservedBytes = 1

// MaxSquareSize is the maximum number of
// rows/columns of the original data shares in square layout.
// Corresponds to AVAILABLE_DATA_ORIGINAL_SQUARE_MAX in the spec.
Expand Down
74 changes: 63 additions & 11 deletions types/shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,35 +56,87 @@ func (m Message) MarshalDelimited() ([]byte, error) {
return append(lenBuf[:n], m.Data...), nil
}

func appendToShares(shares []NamespacedShare, nid namespace.ID, rawData []byte, shareSize int) []NamespacedShare {
if len(rawData) < shareSize {
rawShare := rawData
paddedShare := zeroPadIfNecessary(rawShare, shareSize)
// appendToSharesContiguous appends one raw data separately as shares
// Used for messages
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
func appendToShares(shares []NamespacedShare, nid namespace.ID, rawData []byte) []NamespacedShare {
const adjustedSize = ShareSize - NamespaceSize
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
if len(rawData) < adjustedSize {
rawShare := []byte(append(nid, rawData...))
paddedShare := zeroPadIfNecessary(rawShare, ShareSize)
share := NamespacedShare{paddedShare, nid}
shares = append(shares, share)
} else { // len(rawData) >= adjustedSize
shares = append(shares, split(rawData, nid)...)
}
return shares
}

// appendToSharesContiguous appends multiple raw data contiguously as shares
// Used for transactions, intermediate state roots, and evidence
func appendToSharesContiguous(shares []NamespacedShare, nid namespace.ID, rawDatas [][]byte) []NamespacedShare {
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
const adjustedSize = ShareSize - NamespaceSize - ShareReservedBytes
// Index into the outer slice of rawDatas
outerIndex := 0
// Index into the inner slice of rawDatas
innerIndex := 0
for outerIndex < len(rawDatas) {
var rawData []byte
startIndex := 0
rawData, outerIndex, innerIndex, startIndex = getNextChunk(rawDatas, outerIndex, innerIndex, adjustedSize)
rawShare := []byte(append(append(nid, byte(startIndex)), rawData...))
paddedShare := zeroPadIfNecessary(rawShare, ShareSize)
share := NamespacedShare{paddedShare, nid}
shares = append(shares, share)
} else { // len(rawData) >= shareSize
shares = append(shares, split(rawData, shareSize, nid)...)
}
return shares
}

// TODO(ismail): implement corresponding merge method for clients requesting
// shares for a particular namespace
func split(rawData []byte, shareSize int, nid namespace.ID) []NamespacedShare {
func split(rawData []byte, nid namespace.ID) []NamespacedShare {
const adjustedSize = ShareSize - NamespaceSize
shares := make([]NamespacedShare, 0)
firstRawShare := rawData[:shareSize]
firstRawShare := []byte(append(nid, rawData[:adjustedSize]...))
shares = append(shares, NamespacedShare{firstRawShare, nid})
rawData = rawData[shareSize:]
rawData = rawData[adjustedSize:]
for len(rawData) > 0 {
shareSizeOrLen := min(shareSize, len(rawData))
paddedShare := zeroPadIfNecessary(rawData[:shareSizeOrLen], shareSize)
shareSizeOrLen := min(adjustedSize, len(rawData))
rawShare := []byte(append(nid, rawData[:shareSizeOrLen]...))
paddedShare := zeroPadIfNecessary(rawShare, ShareSize)
share := NamespacedShare{paddedShare, nid}
shares = append(shares, share)
rawData = rawData[shareSizeOrLen:]
}
return shares
}

func getNextChunk(rawDatas [][]byte, outerIndex int, innerIndex int, width int) ([]byte, int, int, int) {
rawData := make([]byte, 0, width)
startIndex := len(rawDatas[outerIndex]) - innerIndex - 1
// If the start index would go past the end of the share, no transaction begins in this share
if startIndex >= width {
startIndex = 0
}
// Offset by the fixed reserved bytes at the beginning of the share
if startIndex > 0 {
startIndex += NamespaceSize + ShareReservedBytes
}

curIndex := 0
for curIndex < width && outerIndex < len(rawDatas) {
bytesToFetch := min(len(rawDatas[outerIndex])-innerIndex-1, width-curIndex-1)
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved
rawData = append(rawData, rawDatas[outerIndex][innerIndex:innerIndex+bytesToFetch]...)
innerIndex += bytesToFetch
if innerIndex >= len(rawDatas[outerIndex]) {
innerIndex = 0
outerIndex++
}
curIndex += bytesToFetch
}

return rawData, outerIndex, innerIndex, startIndex
}

func GenerateTailPaddingShares(n int, shareWidth int) NamespacedShares {
shares := make([]NamespacedShare, n)
for i := 0; i < n; i++ {
Expand Down
22 changes: 8 additions & 14 deletions types/shares_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import (
)

type splitter interface {
splitIntoShares(shareSize int) NamespacedShares
splitIntoShares() NamespacedShares
}
adlerjohn marked this conversation as resolved.
Show resolved Hide resolved

func TestMakeShares(t *testing.T) {
reservedTxNamespaceID := append(bytes.Repeat([]byte{0}, 7), 1)
reservedEvidenceNamespaceID := append(bytes.Repeat([]byte{0}, 7), 3)
// resveredIntermediateStateRootsNamespaceID := append(bytes.Repeat([]byte{0}, 7), 2)
val := NewMockPV()
blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash"))
blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash"))
Expand All @@ -37,12 +36,11 @@ func TestMakeShares(t *testing.T) {
}
msg1Marshaled, _ := msg1.MarshalDelimited()
if err != nil {
t.Fatalf("Could not encode evidence: %v, error: %v", testEvidence, err)
t.Fatalf("Could not encode evidence: %v, error: %v\n", testEvidence, err)
}

type args struct {
data splitter
shareSize int
data splitter
}
tests := []struct {
name string
Expand All @@ -54,7 +52,6 @@ func TestMakeShares(t *testing.T) {
data: &EvidenceData{
Evidence: []Evidence{testEvidence},
},
shareSize: ShareSize,
}, NamespacedShares{NamespacedShare{
Share: testEvidenceBytes[:ShareSize],
ID: reservedEvidenceNamespaceID,
Expand All @@ -65,8 +62,7 @@ func TestMakeShares(t *testing.T) {
},
{"small LL Tx",
args{
data: Txs{smolTx},
shareSize: ShareSize,
data: Txs{smolTx},
},
NamespacedShares{
NamespacedShare{
Expand All @@ -77,8 +73,7 @@ func TestMakeShares(t *testing.T) {
},
{"one large LL Tx",
args{
data: Txs{largeTx},
shareSize: ShareSize,
data: Txs{largeTx},
},
NamespacedShares{
NamespacedShare{
Expand All @@ -93,8 +88,7 @@ func TestMakeShares(t *testing.T) {
},
{"ll-app message",
args{
data: Messages{[]Message{msg1}},
shareSize: ShareSize,
data: Messages{[]Message{msg1}},
},
NamespacedShares{
NamespacedShare{zeroPadIfNecessary(msg1Marshaled, ShareSize), msg1.NamespaceID},
Expand All @@ -105,8 +99,8 @@ func TestMakeShares(t *testing.T) {
tt := tt // stupid scopelint :-/
i := i
t.Run(tt.name, func(t *testing.T) {
if got := tt.args.data.splitIntoShares(tt.args.shareSize); !reflect.DeepEqual(got, tt.want) {
t.Errorf("%v: makeShares() = \n%v\nwant\n%v", i, got, tt.want)
if got := tt.args.data.splitIntoShares(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("%v: makeShares() = \n%v\nwant\n%v\n", i, got, tt.want)
}
})
}
Expand Down
6 changes: 4 additions & 2 deletions types/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,17 @@ func (txs Txs) Proof(i int) TxProof {
}
}

func (txs Txs) splitIntoShares(shareSize int) NamespacedShares {
func (txs Txs) splitIntoShares() NamespacedShares {
shares := make([]NamespacedShare, 0)
rawDatas := make([][]byte, 0, len(txs))
for _, tx := range txs {
rawData, err := tx.MarshalDelimited()
if err != nil {
panic(fmt.Sprintf("included Tx in mem-pool that can not be encoded %v", tx))
}
shares = appendToShares(shares, TxNamespaceID, rawData, shareSize)
rawDatas = append(rawDatas, rawData)
}
shares = appendToSharesContiguous(shares, TxNamespaceID, rawDatas)
return shares
}

Expand Down