Skip to content

Commit

Permalink
tests(share/byzantine): extend befp tests (#2864)
Browse files Browse the repository at this point in the history
  • Loading branch information
vgonkivs committed Oct 25, 2023
1 parent 43e6bbf commit 565691f
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 20 deletions.
48 changes: 35 additions & 13 deletions share/eds/byzantine/bad_encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,31 @@ func (p *BadEncodingProof) UnmarshalBinary(data []byte) error {
return nil
}

var (
errHeightMismatch = errors.New("height reported in proof does not match with the header's height")
errIncorrectIndex = errors.New("row/col index is more then the roots amount")
errIncorrectAmountOfShares = errors.New("incorrect amount of shares")
errIncorrectShare = errors.New("incorrect share received")
errNMTTreeRootsMatch = errors.New("recomputed root matches the DAH root")
)

var (
invalidProofPrefix = fmt.Sprintf("invalid %s proof", BadEncoding)
)

// Validate ensures that fraud proof is correct.
// Validate checks that provided Merkle Proofs correspond to the shares,
// rebuilds bad row or col from received shares, computes Merkle Root
// and compares it with block's Merkle Root.
func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
if hdr.Height() != p.BlockHeight {
return fmt.Errorf("incorrect block height during BEFP validation: expected %d, got %d",
p.BlockHeight, hdr.Height(),
log.Debugf("%s: %s. expected block's height: %d, got: %d",
invalidProofPrefix,
errHeightMismatch,
hdr.Height(),
p.BlockHeight,
)
return errHeightMismatch
}

if len(hdr.DAH.RowRoots) != len(hdr.DAH.ColumnRoots) {
Expand All @@ -132,19 +148,21 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
}

if int(p.Index) >= len(merkleRoots) {
return fmt.Errorf("invalid %s proof: index out of bounds (%d >= %d)",
BadEncoding, int(p.Index), len(merkleRoots),
log.Debugf("%s:%s (%d >= %d)",
invalidProofPrefix, errIncorrectIndex, int(p.Index), len(merkleRoots),
)
return errIncorrectIndex
}

if len(p.Shares) != len(merkleRoots) {
// 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.
return fmt.Errorf("invalid %s proof: incorrect number of shares %d != %d",
BadEncoding, len(p.Shares), len(merkleRoots),
log.Infof("%s: %s (%d >= %d)",
invalidProofPrefix, errIncorrectAmountOfShares, int(p.Index), len(merkleRoots),
)
return errIncorrectAmountOfShares
}

odsWidth := uint64(len(merkleRoots) / 2)
Expand All @@ -160,7 +178,9 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
}

if amount < odsWidth {
return errors.New("fraud: invalid proof: not enough shares provided to reconstruct row/col")
log.Debugf("%s: %s. not enough shares provided to reconstruct row/col",
invalidProofPrefix, errIncorrectAmountOfShares)
return errIncorrectAmountOfShares
}

// verify that Merkle proofs correspond to particular shares.
Expand All @@ -171,7 +191,8 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
}
// validate inclusion of the share into one of the DAHeader roots
if ok := shr.Validate(ipld.MustCidFromNamespacedSha256(merkleRoots[index])); !ok {
return fmt.Errorf("invalid %s proof: incorrect share received at index %d", BadEncoding, index)
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 ¯\_(ツ)_/¯
Expand All @@ -184,15 +205,15 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
// the row/col can't be reconstructed, or the building of NMTree fails.
rebuiltShares, err := codec.Decode(shares)
if err != nil {
log.Infow("failed to decode shares at height",
log.Debugw("failed to decode shares at height",
"height", hdr.Height(), "err", err,
)
return nil
}

rebuiltExtendedShares, err := codec.Encode(rebuiltShares[0:odsWidth])
if err != nil {
log.Infow("failed to encode shares at height",
log.Debugw("failed to encode shares at height",
"height", hdr.Height(), "err", err,
)
return nil
Expand All @@ -203,7 +224,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {
for _, share := range rebuiltShares {
err = tree.Push(share)
if err != nil {
log.Infow("failed to build a tree from the reconstructed shares at height",
log.Debugw("failed to build a tree from the reconstructed shares at height",
"height", hdr.Height(), "err", err,
)
return nil
Expand All @@ -212,7 +233,7 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {

expectedRoot, err := tree.Root()
if err != nil {
log.Infow("failed to build a tree root at height",
log.Debugw("failed to build a tree root at height",
"height", hdr.Height(), "err", err,
)
return nil
Expand All @@ -226,7 +247,8 @@ func (p *BadEncodingProof) Validate(hdr *header.ExtendedHeader) error {

// comparing rebuilt Merkle Root of bad row/col with respective Merkle Root of row/col from block.
if bytes.Equal(expectedRoot, root) {
return fmt.Errorf("invalid %s proof: recomputed Merkle root matches the DAH's row/column root", BadEncoding)
log.Debugf("invalid %s proof:%s", BadEncoding, errNMTTreeRootsMatch)
return errNMTTreeRootsMatch
}
return nil
}
153 changes: 146 additions & 7 deletions share/eds/byzantine/bad_encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
core "github.com/tendermint/tendermint/types"

"github.com/celestiaorg/celestia-app/pkg/da"
"github.com/celestiaorg/celestia-app/test/util/malicious"
"github.com/celestiaorg/nmt"
"github.com/celestiaorg/rsmt2d"

"github.com/celestiaorg/celestia-node/header"
Expand All @@ -19,8 +20,8 @@ import (
"github.com/celestiaorg/celestia-node/share/sharetest"
)

func TestBadEncodingFraudProof(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
func TestBEFP_Validate(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer t.Cleanup(cancel)
bServ := ipld.NewMemBlockservice()

Expand All @@ -37,10 +38,115 @@ func TestBadEncodingFraudProof(t *testing.T) {
errByz := NewErrByzantine(ctx, bServ, &dah, errRsmt2d)

befp := CreateBadEncodingProof([]byte("hash"), 0, errByz)
err = befp.Validate(&header.ExtendedHeader{
DAH: &dah,
})
assert.NoError(t, err)

var test = []struct {
name string
prepareFn func() error
expectedResult func(error)
}{
{
name: "valid BEFP",
prepareFn: func() error {
return befp.Validate(&header.ExtendedHeader{DAH: &dah})
},
expectedResult: func(err error) {
require.NoError(t, err)
},
},
{
name: "invalid BEFP for valid header",
prepareFn: func() error {
validSquare := edstest.RandEDS(t, 2)
validDah, err := da.NewDataAvailabilityHeader(validSquare)
require.NoError(t, err)
err = ipld.ImportEDS(ctx, validSquare, bServ)
require.NoError(t, err)
validShares := validSquare.Flattened()
errInvalidByz := NewErrByzantine(ctx, bServ, &validDah,
&rsmt2d.ErrByzantineData{
Axis: rsmt2d.Row,
Index: 0,
Shares: validShares[0:4],
},
)
invalidBefp := CreateBadEncodingProof([]byte("hash"), 0, errInvalidByz)
return invalidBefp.Validate(&header.ExtendedHeader{DAH: &validDah})
},
expectedResult: func(err error) {
require.ErrorIs(t, err, errNMTTreeRootsMatch)
},
},
{
name: "incorrect share with Proof",
prepareFn: func() error {
befp, ok := befp.(*BadEncodingProof)
require.True(t, ok)
befp.Shares[0].Share = befp.Shares[1].Share
return befp.Validate(&header.ExtendedHeader{DAH: &dah})
},
expectedResult: func(err error) {
require.ErrorIs(t, err, errIncorrectShare)
},
},
{
name: "invalid amount of shares",
prepareFn: func() error {
befp, ok := befp.(*BadEncodingProof)
require.True(t, ok)
befp.Shares = befp.Shares[0 : len(befp.Shares)/2]
return befp.Validate(&header.ExtendedHeader{DAH: &dah})
},
expectedResult: func(err error) {
require.ErrorIs(t, err, errIncorrectAmountOfShares)
},
},
{
name: "not enough shares to recompute the root",
prepareFn: func() error {
befp, ok := befp.(*BadEncodingProof)
require.True(t, ok)
befp.Shares[0] = nil
return befp.Validate(&header.ExtendedHeader{DAH: &dah})
},
expectedResult: func(err error) {
require.ErrorIs(t, err, errIncorrectAmountOfShares)
},
},
{
name: "index out of bounds",
prepareFn: func() error {
befp, ok := befp.(*BadEncodingProof)
require.True(t, ok)
befpCopy := *befp
befpCopy.Index = 100
return befpCopy.Validate(&header.ExtendedHeader{DAH: &dah})
},
expectedResult: func(err error) {
require.ErrorIs(t, err, errIncorrectIndex)
},
},
{
name: "heights mismatch",
prepareFn: func() error {
return befp.Validate(&header.ExtendedHeader{
RawHeader: core.Header{
Height: 42,
},
DAH: &dah,
})
},
expectedResult: func(err error) {
require.ErrorIs(t, err, errHeightMismatch)
},
},
}

for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
err = tt.prepareFn()
tt.expectedResult(err)
})
}
}

// TestIncorrectBadEncodingFraudProof asserts that BEFP is not generated for the correct data
Expand Down Expand Up @@ -90,3 +196,36 @@ func TestIncorrectBadEncodingFraudProof(t *testing.T) {
err = proof.Validate(h)
require.Error(t, err)
}

func TestBEFP_ValidateOutOfOrderShares(t *testing.T) {
// skipping it for now because `malicious` package has a small issue: Constructor does not apply
// passed options, so it's not possible to store shares and thus get proofs for them.
// should be ok once app team will fix it.
t.Skip()
eds := edstest.RandEDS(t, 16)
shares := eds.Flattened()
shares[0], shares[1] = shares[1], shares[0] // corrupting eds
bServ := ipld.NewMemBlockservice()
batchAddr := ipld.NewNmtNodeAdder(context.Background(), bServ, ipld.MaxSizeBatchOption(16*2))
eds, err := rsmt2d.ImportExtendedDataSquare(shares,
share.DefaultRSMT2DCodec(),
malicious.NewConstructor(16, nmt.NodeVisitor(batchAddr.Visit)),
)
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)

errByz := NewErrByzantine(context.Background(), bServ, &dah, errRsmt2d)

befp := CreateBadEncodingProof([]byte("hash"), 0, errByz)
err = befp.Validate(&header.ExtendedHeader{DAH: &dah})
require.Error(t, err)
}

0 comments on commit 565691f

Please sign in to comment.