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 all 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
2 changes: 1 addition & 1 deletion blockchain/msgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func TestBlockchainMessageVectors(t *testing.T) {
BlockRequest: &bcproto.BlockRequest{Height: math.MaxInt64}}},
"0a0a08ffffffffffffffff7f"},
{"BlockResponseMessage", &bcproto.Message{Sum: &bcproto.Message_BlockResponse{
BlockResponse: &bcproto.BlockResponse{Block: bpb}}}, "1ac0020abd020a5b0a02080b1803220b088092b8c398feffffff012a0212003a2016106406aededa633a265efd5833af53775af5a27c803518313c9b77e68875926a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85512130a0b48656c6c6f20576f726c6412001a0022001ac8010a3000000000000000010000000000000001266fcca850585e31b54074399b0dbb5b04b408b77d5f357a528ed3e04b63c3c10a30ffffffffffffffffffffffffffffffffe04e02811b28234428f1c1219d5627f61c02c415c175bdabd9e7934519baef07123000000000000000010000000000000001266fcca850585e31b54074399b0dbb5b04b408b77d5f357a528ed3e04b63c3c11230ffffffffffffffffffffffffffffffffe04e02811b28234428f1c1219d5627f61c02c415c175bdabd9e7934519baef07"},
BlockResponse: &bcproto.BlockResponse{Block: bpb}}}, "1ac0020abd020a5b0a02080b1803220b088092b8c398feffffff012a0212003a204c149a7cfadc92b669b0cbfa4951a1b18c2d9f3177a3b8756d39ebb96e9d63316a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85512130a0b48656c6c6f20576f726c6412001a0022001ac8010a3000000000000000010000000000000001b81cb5596c28d044214b9f935e4af7dbe76e417f6182d86fbee68bfff7b2ff3a0a30ffffffffffffffffffffffffffffffffc4096ba8fccf882c309896e9168fa43fe62fccb752cb12d5160cc1d9c2ebffe7123000000000000000010000000000000001b81cb5596c28d044214b9f935e4af7dbe76e417f6182d86fbee68bfff7b2ff3a1230ffffffffffffffffffffffffffffffffc4096ba8fccf882c309896e9168fa43fe62fccb752cb12d5160cc1d9c2ebffe7"},
{"NoBlockResponseMessage", &bcproto.Message{Sum: &bcproto.Message_NoBlockResponse{
NoBlockResponse: &bcproto.NoBlockResponse{Height: 1}}}, "12020801"},
{"NoBlockResponseMessage", &bcproto.Message{Sum: &bcproto.Message_NoBlockResponse{
Expand Down
33 changes: 18 additions & 15 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,43 @@ type IntermediateStateRoots struct {
RawRootsList []tmbytes.HexBytes `json:"intermediate_roots"`
}

func (roots IntermediateStateRoots) splitIntoShares(shareSize int) NamespacedShares {
shares := make([]NamespacedShare, 0)
func (roots IntermediateStateRoots) splitIntoShares() NamespacedShares {
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 := splitContiguous(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 +1589,8 @@ func (data *EvidenceData) FromProto(eviData *tmproto.EvidenceList) error {
return nil
}

func (data *EvidenceData) splitIntoShares(shareSize int) NamespacedShares {
shares := make([]NamespacedShare, 0)
func (data *EvidenceData) splitIntoShares() NamespacedShares {
rawDatas := make([][]byte, 0, len(data.Evidence))
for _, ev := range data.Evidence {
var rawData []byte
var err error
Expand All @@ -1608,8 +1610,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 := splitContiguous(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
8 changes: 8 additions & 0 deletions types/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ const (
// NamespaceSize is the namespace size in bytes.
NamespaceSize = 8

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

// TxShareSize is the number of bytes usable for tx/evidence/ISR shares.
TxShareSize = ShareSize - NamespaceSize - ShareReservedBytes
// MsgShareSize is the number of bytes usable for message shares.
MsgShareSize = ShareSize - NamespaceSize

// 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
80 changes: 69 additions & 11 deletions types/shares.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,35 +56,93 @@ 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)
// appendToShares appends raw data as shares.
// Used for messages.
func appendToShares(shares []NamespacedShare, nid namespace.ID, rawData []byte) []NamespacedShare {
if len(rawData) < MsgShareSize {
rawShare := []byte(append(nid, rawData...))
paddedShare := zeroPadIfNecessary(rawShare, ShareSize)
share := NamespacedShare{paddedShare, nid}
shares = append(shares, share)
} else { // len(rawData) >= MsgShareSize
shares = append(shares, split(rawData, nid)...)
}
return shares
}

// splitContiguous splits multiple raw data contiguously as shares.
// Used for transactions, intermediate state roots, and evidence.
func splitContiguous(nid namespace.ID, rawDatas [][]byte) []NamespacedShare {
shares := make([]NamespacedShare, 0)
// 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, TxShareSize)
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 {
shares := make([]NamespacedShare, 0)
firstRawShare := rawData[:shareSize]
firstRawShare := []byte(append(nid, rawData[:MsgShareSize]...))
shares = append(shares, NamespacedShare{firstRawShare, nid})
rawData = rawData[shareSize:]
rawData = rawData[MsgShareSize:]
for len(rawData) > 0 {
shareSizeOrLen := min(shareSize, len(rawData))
paddedShare := zeroPadIfNecessary(rawData[:shareSizeOrLen], shareSize)
shareSizeOrLen := min(MsgShareSize, 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
}

// getNextChunk gets the next chunk for contiguous shares
// Precondition: none of the slices in rawDatas is zero-length
// This precondition should always hold at this point since zero-length txs are simply invalid.
func getNextChunk(rawDatas [][]byte, outerIndex int, innerIndex int, width int) ([]byte, int, int, int) {
rawData := make([]byte, 0, width)
startIndex := 0
firstBytesToFetch := 0

curIndex := 0
for curIndex < width && outerIndex < len(rawDatas) {
bytesToFetch := min(len(rawDatas[outerIndex])-innerIndex, width-curIndex)
if bytesToFetch == 0 {
panic("zero-length contiguous share data is invalid")
}
if curIndex == 0 {
firstBytesToFetch = bytesToFetch
}
// If we've already placed some data in this chunk, that means
// a new data segment begins
if curIndex != 0 {
// Offset by the fixed reserved bytes at the beginning of the share
startIndex = firstBytesToFetch + NamespaceSize + ShareReservedBytes
}
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
90 changes: 65 additions & 25 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,59 +52,101 @@ func TestMakeShares(t *testing.T) {
data: &EvidenceData{
Evidence: []Evidence{testEvidence},
},
shareSize: ShareSize,
}, NamespacedShares{NamespacedShare{
Share: testEvidenceBytes[:ShareSize],
ID: reservedEvidenceNamespaceID,
Share: append(
append(reservedEvidenceNamespaceID, byte(0)),
testEvidenceBytes[:TxShareSize]...,
),
ID: reservedEvidenceNamespaceID,
}, NamespacedShare{
Share: zeroPadIfNecessary(testEvidenceBytes[ShareSize:], ShareSize),
ID: reservedEvidenceNamespaceID,
Share: append(
append(reservedEvidenceNamespaceID, byte(0)),
zeroPadIfNecessary(testEvidenceBytes[TxShareSize:], TxShareSize)...,
),
ID: reservedEvidenceNamespaceID,
}},
},
{"small LL Tx",
args{
data: Txs{smolTx},
shareSize: ShareSize,
data: Txs{smolTx},
},
NamespacedShares{
NamespacedShare{
Share: zeroPadIfNecessary(smolTxLenDelimited, ShareSize),
ID: reservedTxNamespaceID,
Share: append(
append(reservedTxNamespaceID, byte(0)),
zeroPadIfNecessary(smolTxLenDelimited, TxShareSize)...,
),
ID: reservedTxNamespaceID,
},
},
},
{"one large LL Tx",
args{
data: Txs{largeTx},
shareSize: ShareSize,
data: Txs{largeTx},
},
NamespacedShares{
NamespacedShare{
Share: Share(largeTxLenDelimited[:ShareSize]),
ID: reservedTxNamespaceID,
Share: append(
append(reservedTxNamespaceID, byte(0)),
largeTxLenDelimited[:TxShareSize]...,
),
ID: reservedTxNamespaceID,
},
NamespacedShare{
Share: zeroPadIfNecessary(largeTxLenDelimited[ShareSize:], ShareSize),
ID: reservedTxNamespaceID,
Share: append(
append(reservedTxNamespaceID, byte(0)),
zeroPadIfNecessary(largeTxLenDelimited[TxShareSize:], TxShareSize)...,
),
ID: reservedTxNamespaceID,
},
},
},
{"large then small LL Tx",
args{
data: Txs{largeTx, smolTx},
},
NamespacedShares{
NamespacedShare{
Share: append(
append(reservedTxNamespaceID, byte(0)),
largeTxLenDelimited[:TxShareSize]...,
),
ID: reservedTxNamespaceID,
},
NamespacedShare{
Share: append(
append(reservedTxNamespaceID, byte(len(largeTxLenDelimited)-TxShareSize+NamespaceSize+ShareReservedBytes)),
zeroPadIfNecessary(
append(largeTxLenDelimited[TxShareSize:], smolTxLenDelimited...),
TxShareSize,
)...,
),
ID: reservedTxNamespaceID,
},
},
},
{"ll-app message",
args{
data: Messages{[]Message{msg1}},
shareSize: ShareSize,
data: Messages{[]Message{msg1}},
},
NamespacedShares{
NamespacedShare{zeroPadIfNecessary(msg1Marshaled, ShareSize), msg1.NamespaceID},
NamespacedShare{
Share: append(
[]byte(msg1.NamespaceID),
zeroPadIfNecessary(msg1Marshaled, MsgShareSize)...,
),
ID: msg1.NamespaceID,
},
},
},
}
for i, tt := range tests {
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)
got := tt.args.data.splitIntoShares()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("%v: makeShares() = \n%+v\nwant\n%+v\n", i, got, tt.want)
}
})
}
Expand Down
Loading