Skip to content

Commit

Permalink
fix!(share/byzantine): use any available axis for befp nmt proofs con…
Browse files Browse the repository at this point in the history
…struction (#3306)

We should not restrict the BEFP constructor to collect proof only for orthogonal axes to ErrByzantine. This PR enables the constructor to attempt building both Row and Col proofs, irrespective of the ErrByzantine axis type. Additionally, it prevents the BEFP constructor from requesting proofs pr shars from the network by granting access exclusively to the local blockstore. There should be sufficient proofs and shares in the local blockstore at the time BEFP is detected.

Breaks befp message, by introducing proofAxis field, which is not supported by older befp subscribers.

Depends on 
- #3305 to have enough data in blockstore.
- celestiaorg/rsmt2d#310 to provide proper coordinates of verified shares
  • Loading branch information
walldiss authored Apr 23, 2024
1 parent 8bc46ce commit 116af74
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 189 deletions.
32 changes: 11 additions & 21 deletions share/eds/byzantine/bad_encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/celestiaorg/celestia-node/header"
"github.com/celestiaorg/celestia-node/share"
pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb"
"github.com/celestiaorg/celestia-node/share/ipld"
)

const (
Expand Down Expand Up @@ -139,34 +138,27 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
)
}

// merkleRoots are the roots against which we are going to check the inclusion of the received
// shares. Changing the order of the roots to prove the shares relative to the orthogonal axis,
// because inside the rsmt2d library rsmt2d.Row = 0 and rsmt2d.Col = 1
merkleRoots := hdr.DAH.RowRoots
if p.Axis == rsmt2d.Row {
merkleRoots = hdr.DAH.ColumnRoots
}

if int(p.Index) >= len(merkleRoots) {
width := len(hdr.DAH.RowRoots)
if int(p.Index) >= width {
log.Debugf("%s:%s (%d >= %d)",
invalidProofPrefix, errIncorrectIndex, int(p.Index), len(merkleRoots),
invalidProofPrefix, errIncorrectIndex, int(p.Index), width,
)
return errIncorrectIndex
}

if len(p.Shares) != len(merkleRoots) {
if len(p.Shares) != width {
// Since p.Shares should contain all the shares from either a row or a
// column, it should exactly match the number of row roots. In this
// context, the number of row roots is the width of the extended data
// square.
log.Infof("%s: %s (%d >= %d)",
invalidProofPrefix, errIncorrectAmountOfShares, int(p.Index), len(merkleRoots),
invalidProofPrefix, errIncorrectAmountOfShares, int(p.Index), width,
)
return errIncorrectAmountOfShares
}

odsWidth := uint64(len(merkleRoots) / 2)
amount := uint64(0)
odsWidth := width / 2
var amount int
for _, share := range p.Shares {
if share == nil {
continue
Expand All @@ -184,19 +176,17 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
}

// verify that Merkle proofs correspond to particular shares.
shares := make([][]byte, len(merkleRoots))
shares := make([][]byte, width)
for index, shr := range p.Shares {
if shr == nil {
continue
}
// validate inclusion of the share into one of the DAHeader roots
if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(merkleRoots[index])); !ok {
if ok := shr.Validate(hdr.DAH, p.Axis, int(p.Index), index); !ok {
log.Debugf("%s: %s at index %d", invalidProofPrefix, errIncorrectShare, index)
return errIncorrectShare
}
// NMTree commits the additional namespace while rsmt2d does not know about, so we trim it
// this is ugliness from NMTWrapper that we have to embrace ¯\_(ツ)_/¯
shares[index] = share.GetData(shr.Share)
shares[index] = shr.Share
}

codec := share.DefaultRSMT2DCodec()
Expand All @@ -220,7 +210,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
}
copy(rebuiltShares[odsWidth:], rebuiltExtendedShares)

tree := wrapper.NewErasuredNamespacedMerkleTree(odsWidth, uint(p.Index))
tree := wrapper.NewErasuredNamespacedMerkleTree(uint64(odsWidth), uint(p.Index))
for _, share := range rebuiltShares {
err = tree.Push(share)
if err != nil {
Expand Down
31 changes: 16 additions & 15 deletions share/eds/byzantine/bad_encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestBEFP_Validate(t *testing.T) {
err = square.Repair(dah.RowRoots, dah.ColumnRoots)
require.ErrorAs(t, err, &errRsmt2d)

byzantine := NewErrByzantine(ctx, bServ, &dah, errRsmt2d)
byzantine := NewErrByzantine(ctx, bServ.Blockstore(), &dah, errRsmt2d)
var errByz *ErrByzantine
require.ErrorAs(t, byzantine, &errByz)

Expand Down Expand Up @@ -71,7 +71,7 @@ func TestBEFP_Validate(t *testing.T) {
err = ipld.ImportEDS(ctx, validSquare, bServ)
require.NoError(t, err)
validShares := validSquare.Flattened()
errInvalidByz := NewErrByzantine(ctx, bServ, &validDah,
errInvalidByz := NewErrByzantine(ctx, bServ.Blockstore(), &validDah,
&rsmt2d.ErrByzantineData{
Axis: rsmt2d.Row,
Index: 0,
Expand All @@ -93,7 +93,7 @@ func TestBEFP_Validate(t *testing.T) {
// break the first shareWithProof to test negative case
sh := sharetest.RandShares(t, 2)
nmtProof := nmt.NewInclusionProof(0, 1, nil, false)
befp.Shares[0] = &ShareWithProof{sh[0], &nmtProof}
befp.Shares[0] = &ShareWithProof{sh[0], &nmtProof, rsmt2d.Row}
return proof.Validate(&header.ExtendedHeader{DAH: &dah})
},
expectedResult: func(err error) {
Expand Down Expand Up @@ -171,16 +171,17 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) {
require.NoError(t, err)

// get an arbitrary row
row := uint(squareSize / 2)
rowShares := eds.Row(row)
rowRoot := dah.RowRoots[row]

shareProofs, err := GetProofsForShares(ctx, bServ, ipld.MustCidFromNamespacedSha256(rowRoot), rowShares)
require.NoError(t, err)
rowIdx := squareSize / 2
shareProofs := make([]*ShareWithProof, 0, eds.Width())
for i := range shareProofs {
proof, err := GetShareWithProof(ctx, bServ, dah, shares[i], rsmt2d.Row, rowIdx, i)
require.NoError(t, err)
shareProofs = append(shareProofs, proof)
}

// create a fake error for data that was encoded correctly
fakeError := ErrByzantine{
Index: uint32(row),
Index: uint32(rowIdx),
Shares: shareProofs,
Axis: rsmt2d.Row,
}
Expand All @@ -203,7 +204,7 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) {
}

func TestBEFP_ValidateOutOfOrderShares(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
t.Cleanup(cancel)

size := 4
Expand All @@ -221,17 +222,17 @@ func TestBEFP_ValidateOutOfOrderShares(t *testing.T) {
)
require.NoError(t, err, "failure to recompute the extended data square")

err = batchAddr.Commit()
require.NoError(t, err)

dah, err := da.NewDataAvailabilityHeader(eds)
require.NoError(t, err)

var errRsmt2d *rsmt2d.ErrByzantineData
err = eds.Repair(dah.RowRoots, dah.ColumnRoots)
require.ErrorAs(t, err, &errRsmt2d)

byzantine := NewErrByzantine(ctx, bServ, &dah, errRsmt2d)
err = batchAddr.Commit()
require.NoError(t, err)

byzantine := NewErrByzantine(ctx, bServ.Blockstore(), &dah, errRsmt2d)
var errByz *ErrByzantine
require.ErrorAs(t, byzantine, &errByz)

Expand Down
55 changes: 20 additions & 35 deletions share/eds/byzantine/byzantine.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"fmt"

"github.com/ipfs/boxo/blockservice"
"github.com/ipfs/boxo/blockstore"

"github.com/celestiaorg/celestia-app/pkg/da"
"github.com/celestiaorg/rsmt2d"
Expand All @@ -31,50 +31,35 @@ func (e *ErrByzantine) Error() string {
// If error happens during proof collection, it terminates the process with os.Exit(1).
func NewErrByzantine(
ctx context.Context,
bGetter blockservice.BlockGetter,
bStore blockstore.Blockstore,
dah *da.DataAvailabilityHeader,
errByz *rsmt2d.ErrByzantineData,
) error {
// changing the order to collect proofs against an orthogonal axis
roots := [][][]byte{
dah.ColumnRoots,
dah.RowRoots,
}[errByz.Axis]

sharesWithProof := make([]*ShareWithProof, len(errByz.Shares))

type result struct {
share *ShareWithProof
index int
}
resultCh := make(chan *result)
bGetter := ipld.NewBlockservice(bStore, nil)
var count int
for index, share := range errByz.Shares {
if share == nil {
if len(share) == 0 {
continue
}
swp, err := GetShareWithProof(ctx, bGetter, dah, share, errByz.Axis, int(errByz.Index), index)
if err != nil {
log.Warn("requesting proof failed",
"errByz", errByz,
"shareIndex", index,
"err", err)
continue
}

index := index
go func() {
share, err := getProofsAt(
ctx, bGetter,
ipld.MustCidFromNamespacedSha256(roots[index]),
int(errByz.Index), len(errByz.Shares),
)
if err != nil {
log.Warn("requesting proof failed", "root", roots[index], "err", err)
return
}
resultCh <- &result{share, index}
}()
sharesWithProof[index] = swp
// it is enough to collect half of the shares to construct the befp
if count++; count >= len(dah.RowRoots)/2 {
break
}
}

for i := 0; i < len(dah.RowRoots)/2; i++ {
select {
case t := <-resultCh:
sharesWithProof[t.index] = t.share
case <-ctx.Done():
return ipld.ErrNodeNotFound
}
if count < len(dah.RowRoots)/2 {
return fmt.Errorf("failed to collect proof")
}

return &ErrByzantine{
Expand Down
80 changes: 58 additions & 22 deletions share/eds/byzantine/pb/share.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions share/eds/byzantine/pb/share.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "pb/proof.proto";
message Share {
bytes Data = 1;
proof.pb.Proof Proof = 2;
axis ProofAxis = 3;
}

enum axis {
Expand Down
Loading

0 comments on commit 116af74

Please sign in to comment.