diff --git a/blob/service_test.go b/blob/service_test.go index 65a4a67359..50b18da3bc 100644 --- a/blob/service_test.go +++ b/blob/service_test.go @@ -3,7 +3,6 @@ package blob import ( "bytes" "context" - "crypto/sha256" "encoding/json" "fmt" "sort" @@ -203,7 +202,7 @@ func TestBlobService_Get(t *testing.T) { for _, p := range *proof { from := to to = p.End() - p.Start() + from - eq := p.VerifyInclusion(sha256.New(), namespace.ToNMT(), rawShares[from:to], row) + eq := p.VerifyInclusion(share.NewSHA256Hasher(), namespace.ToNMT(), rawShares[from:to], row) if eq == true { return } diff --git a/share/eds/byzantine/bad_encoding.go b/share/eds/byzantine/bad_encoding.go index da4c60a252..9c754eaf47 100644 --- a/share/eds/byzantine/bad_encoding.go +++ b/share/eds/byzantine/bad_encoding.go @@ -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 ( @@ -137,34 +136,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 @@ -182,19 +174,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() @@ -218,7 +208,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 { diff --git a/share/eds/byzantine/bad_encoding_test.go b/share/eds/byzantine/bad_encoding_test.go index 4695c8db9a..d54ed23940 100644 --- a/share/eds/byzantine/bad_encoding_test.go +++ b/share/eds/byzantine/bad_encoding_test.go @@ -2,7 +2,6 @@ package byzantine import ( "context" - "crypto/sha256" "hash" "testing" "time" @@ -41,7 +40,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) @@ -71,7 +70,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, @@ -93,7 +92,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) { @@ -171,16 +170,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, } @@ -203,7 +203,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 @@ -221,9 +221,6 @@ 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) @@ -231,7 +228,10 @@ func TestBEFP_ValidateOutOfOrderShares(t *testing.T) { 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) @@ -253,7 +253,7 @@ func newNamespacedBlockService() *namespacedBlockService { sha256NamespaceFlagged := uint64(0x7701) // register the nmt hasher to validate the order of namespaces mhcore.Register(sha256NamespaceFlagged, func() hash.Hash { - nh := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, true) + nh := nmt.NewNmtHasher(share.NewSHA256Hasher(), share.NamespaceSize, true) nh.Reset() return nh }) @@ -266,7 +266,7 @@ func newNamespacedBlockService() *namespacedBlockService { Codec: sha256NamespaceFlagged, MhType: sha256NamespaceFlagged, // equals to NmtHasher.Size() - MhLength: sha256.New().Size() + 2*share.NamespaceSize, + MhLength: share.NewSHA256Hasher().Size() + 2*share.NamespaceSize, } return bs } diff --git a/share/eds/byzantine/byzantine.go b/share/eds/byzantine/byzantine.go index d20b56deed..030453c31a 100644 --- a/share/eds/byzantine/byzantine.go +++ b/share/eds/byzantine/byzantine.go @@ -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" @@ -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{ diff --git a/share/eds/byzantine/pb/share.pb.go b/share/eds/byzantine/pb/share.pb.go index 4186eabc64..af79d48b13 100644 --- a/share/eds/byzantine/pb/share.pb.go +++ b/share/eds/byzantine/pb/share.pb.go @@ -49,8 +49,9 @@ func (Axis) EnumDescriptor() ([]byte, []int) { } type Share struct { - Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"` - Proof *pb.Proof `protobuf:"bytes,2,opt,name=Proof,proto3" json:"Proof,omitempty"` + Data []byte `protobuf:"bytes,1,opt,name=Data,proto3" json:"Data,omitempty"` + Proof *pb.Proof `protobuf:"bytes,2,opt,name=Proof,proto3" json:"Proof,omitempty"` + ProofAxis Axis `protobuf:"varint,3,opt,name=ProofAxis,proto3,enum=share.eds.byzantine.pb.Axis" json:"ProofAxis,omitempty"` } func (m *Share) Reset() { *m = Share{} } @@ -100,6 +101,13 @@ func (m *Share) GetProof() *pb.Proof { return nil } +func (m *Share) GetProofAxis() Axis { + if m != nil { + return m.ProofAxis + } + return Axis_ROW +} + type BadEncoding struct { HeaderHash []byte `protobuf:"bytes,1,opt,name=HeaderHash,proto3" json:"HeaderHash,omitempty"` Height uint64 `protobuf:"varint,2,opt,name=Height,proto3" json:"Height,omitempty"` @@ -187,26 +195,27 @@ func init() { } var fileDescriptor_d28ce8f160a920d1 = []byte{ - // 297 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xc1, 0x4a, 0xec, 0x30, - 0x14, 0x86, 0x9b, 0x3b, 0xed, 0x5c, 0x38, 0xa3, 0xe3, 0x10, 0x64, 0x08, 0xa2, 0xa1, 0x0c, 0x08, - 0xc5, 0x45, 0x2a, 0x15, 0x1f, 0xc0, 0xaa, 0x50, 0x41, 0x18, 0x89, 0x0b, 0xd7, 0x89, 0x89, 0xd3, - 0x6e, 0xda, 0xd2, 0x74, 0x51, 0x7d, 0x0a, 0x1f, 0xca, 0x85, 0xcb, 0x59, 0xba, 0x94, 0xf6, 0x45, - 0xa4, 0x69, 0x11, 0x17, 0xba, 0x3b, 0xdf, 0x9f, 0x2f, 0x3f, 0xc9, 0x81, 0x95, 0x49, 0x45, 0xa5, - 0x43, 0xad, 0x4c, 0x28, 0x9f, 0x5f, 0x44, 0x5e, 0x67, 0xb9, 0x0e, 0x4b, 0x19, 0xda, 0x98, 0x95, - 0x55, 0x51, 0x17, 0x78, 0x39, 0x80, 0x56, 0x86, 0x7d, 0x3b, 0xac, 0x94, 0x07, 0xf3, 0x52, 0x86, - 0x65, 0x55, 0x14, 0x4f, 0x83, 0xb7, 0x8a, 0xc1, 0xbb, 0xef, 0x4d, 0x8c, 0xc1, 0xbd, 0x12, 0xb5, - 0x20, 0xc8, 0x47, 0xc1, 0x0e, 0xb7, 0x33, 0x3e, 0x06, 0xef, 0xae, 0x77, 0xc9, 0x3f, 0x1f, 0x05, - 0xb3, 0x68, 0x8f, 0x8d, 0x37, 0x25, 0xb3, 0x31, 0x1f, 0x4e, 0x57, 0x6f, 0x08, 0x66, 0xb1, 0x50, - 0xd7, 0xf9, 0x63, 0xa1, 0xb2, 0x7c, 0x83, 0x29, 0x40, 0xa2, 0x85, 0xd2, 0x55, 0x22, 0x4c, 0x3a, - 0x16, 0xfe, 0x48, 0xf0, 0x12, 0xa6, 0x89, 0xce, 0x36, 0x69, 0x6d, 0x7b, 0x5d, 0x3e, 0x12, 0x3e, - 0x87, 0xa9, 0x7d, 0x8b, 0x21, 0x13, 0x7f, 0x12, 0xcc, 0xa2, 0x23, 0xf6, 0xfb, 0x27, 0x98, 0xb5, - 0xf8, 0x28, 0xe3, 0x7d, 0xf0, 0x6e, 0x72, 0xa5, 0x1b, 0xe2, 0xfa, 0x28, 0xd8, 0xe5, 0x03, 0xe0, - 0x53, 0x70, 0x2f, 0x9a, 0xcc, 0x10, 0xcf, 0x47, 0xc1, 0x3c, 0x3a, 0xfc, 0xab, 0x4a, 0x34, 0x99, - 0xe1, 0xd6, 0x3c, 0x21, 0xe0, 0xf6, 0x84, 0xff, 0xc3, 0x84, 0xaf, 0x1f, 0x16, 0x4e, 0x3f, 0x5c, - 0xae, 0x6f, 0x17, 0x28, 0x26, 0xef, 0x2d, 0x45, 0xdb, 0x96, 0xa2, 0xcf, 0x96, 0xa2, 0xd7, 0x8e, - 0x3a, 0xdb, 0x8e, 0x3a, 0x1f, 0x1d, 0x75, 0xe4, 0xd4, 0x6e, 0xf1, 0xec, 0x2b, 0x00, 0x00, 0xff, - 0xff, 0xb1, 0x96, 0xb9, 0xbe, 0x93, 0x01, 0x00, 0x00, + // 310 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0x31, 0x4b, 0xc4, 0x30, + 0x1c, 0xc5, 0x1b, 0xaf, 0x3d, 0xf1, 0x7f, 0x7a, 0x1e, 0x41, 0x8e, 0x20, 0x1a, 0xca, 0x81, 0x50, + 0x1c, 0x52, 0x39, 0x71, 0x71, 0xf3, 0x54, 0x38, 0x41, 0x38, 0x89, 0x83, 0x73, 0x62, 0xe2, 0xb5, + 0x4b, 0x5b, 0x9a, 0x0e, 0xd5, 0xc1, 0xcf, 0xe0, 0x87, 0x72, 0x70, 0xbc, 0xd1, 0x51, 0xda, 0x2f, + 0x22, 0x4d, 0x8b, 0x3a, 0x28, 0xb8, 0xbd, 0xf7, 0xf2, 0x0b, 0x79, 0x8f, 0xc0, 0xc4, 0x44, 0x22, + 0xd7, 0xa1, 0x56, 0x26, 0x94, 0x8f, 0x4f, 0x22, 0x29, 0xe2, 0x44, 0x87, 0x99, 0x0c, 0x6d, 0xcc, + 0xb2, 0x3c, 0x2d, 0x52, 0x3c, 0x6e, 0x8d, 0x56, 0x86, 0x7d, 0x31, 0x2c, 0x93, 0xbb, 0xc3, 0x4c, + 0x86, 0x59, 0x9e, 0xa6, 0x0f, 0x2d, 0x37, 0x79, 0x06, 0xef, 0xb6, 0x21, 0x31, 0x06, 0xf7, 0x42, + 0x14, 0x82, 0x20, 0x1f, 0x05, 0x9b, 0xdc, 0x6a, 0x7c, 0x00, 0xde, 0x4d, 0xc3, 0x92, 0x35, 0x1f, + 0x05, 0x83, 0xe9, 0x36, 0xeb, 0x6e, 0x4a, 0x66, 0x63, 0xde, 0x9e, 0xe2, 0x53, 0xd8, 0xb0, 0xe2, + 0xac, 0x8c, 0x0d, 0xe9, 0xf9, 0x28, 0x18, 0x4e, 0xf7, 0xd8, 0xef, 0xef, 0x33, 0x51, 0xc6, 0x86, + 0x7f, 0xe3, 0x93, 0x57, 0x04, 0x83, 0x99, 0x50, 0x97, 0xc9, 0x7d, 0xaa, 0xe2, 0x64, 0x89, 0x29, + 0xc0, 0x5c, 0x0b, 0xa5, 0xf3, 0xb9, 0x30, 0x51, 0x57, 0xe6, 0x47, 0x82, 0xc7, 0xd0, 0x9f, 0xeb, + 0x78, 0x19, 0x15, 0xb6, 0x93, 0xcb, 0x3b, 0x87, 0x4f, 0xa0, 0x6f, 0x77, 0x34, 0x05, 0x7a, 0xc1, + 0x60, 0xba, 0xff, 0x57, 0x01, 0x4b, 0xf1, 0x0e, 0xc6, 0x3b, 0xe0, 0x5d, 0x25, 0x4a, 0x97, 0xc4, + 0xf5, 0x51, 0xb0, 0xc5, 0x5b, 0x83, 0x8f, 0xc0, 0xb5, 0x5b, 0xbc, 0x7f, 0x6c, 0xb1, 0xe4, 0x21, + 0x01, 0xb7, 0x71, 0x78, 0x1d, 0x7a, 0x7c, 0x71, 0x37, 0x72, 0x1a, 0x71, 0xbe, 0xb8, 0x1e, 0xa1, + 0x19, 0x79, 0xab, 0x28, 0x5a, 0x55, 0x14, 0x7d, 0x54, 0x14, 0xbd, 0xd4, 0xd4, 0x59, 0xd5, 0xd4, + 0x79, 0xaf, 0xa9, 0x23, 0xfb, 0xf6, 0x07, 0x8e, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x5d, + 0x8d, 0x6c, 0xcf, 0x01, 0x00, 0x00, } func (m *Share) Marshal() (dAtA []byte, err error) { @@ -229,6 +238,11 @@ func (m *Share) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ProofAxis != 0 { + i = encodeVarintShare(dAtA, i, uint64(m.ProofAxis)) + i-- + dAtA[i] = 0x18 + } if m.Proof != nil { { size, err := m.Proof.MarshalToSizedBuffer(dAtA[:i]) @@ -335,6 +349,9 @@ func (m *Share) Size() (n int) { l = m.Proof.Size() n += 1 + l + sovShare(uint64(l)) } + if m.ProofAxis != 0 { + n += 1 + sovShare(uint64(m.ProofAxis)) + } return n } @@ -471,6 +488,25 @@ func (m *Share) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofAxis", wireType) + } + m.ProofAxis = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowShare + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProofAxis |= Axis(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipShare(dAtA[iNdEx:]) diff --git a/share/eds/byzantine/pb/share.proto b/share/eds/byzantine/pb/share.proto index 33e3dae2c2..52abefe7a2 100644 --- a/share/eds/byzantine/pb/share.proto +++ b/share/eds/byzantine/pb/share.proto @@ -6,6 +6,7 @@ import "pb/proof.proto"; message Share { bytes Data = 1; proof.pb.Proof Proof = 2; + axis ProofAxis = 3; } enum axis { diff --git a/share/eds/byzantine/share_proof.go b/share/eds/byzantine/share_proof.go index 98b58ebbec..dbc687e54b 100644 --- a/share/eds/byzantine/share_proof.go +++ b/share/eds/byzantine/share_proof.go @@ -2,7 +2,8 @@ package byzantine import ( "context" - "crypto/sha256" + "errors" + "math" "github.com/ipfs/boxo/blockservice" "github.com/ipfs/go-cid" @@ -10,6 +11,7 @@ import ( "github.com/celestiaorg/nmt" nmt_pb "github.com/celestiaorg/nmt/pb" + "github.com/celestiaorg/rsmt2d" "github.com/celestiaorg/celestia-node/share" pb "github.com/celestiaorg/celestia-node/share/eds/byzantine/pb" @@ -24,31 +26,31 @@ type ShareWithProof struct { share.Share // Proof is a Merkle Proof of current share Proof *nmt.Proof + // Axis is a proof axis + Axis rsmt2d.Axis } -// NewShareWithProof takes the given leaf and its path, starting from the tree root, -// and computes the nmt.Proof for it. -func NewShareWithProof(index int, share share.Share, pathToLeaf []cid.Cid) *ShareWithProof { - rangeProofs := make([][]byte, 0, len(pathToLeaf)) - for i := len(pathToLeaf) - 1; i >= 0; i-- { - node := ipld.NamespacedSha256FromCID(pathToLeaf[i]) - rangeProofs = append(rangeProofs, node) +// Validate validates inclusion of the share under the given root CID. +func (s *ShareWithProof) Validate(dah *share.Root, axisType rsmt2d.Axis, axisIdx, shrIdx int) bool { + var rootHash []byte + switch axisType { + case rsmt2d.Row: + rootHash = rootHashForCoordinates(dah, s.Axis, shrIdx, axisIdx) + case rsmt2d.Col: + rootHash = rootHashForCoordinates(dah, s.Axis, axisIdx, shrIdx) } - proof := nmt.NewInclusionProof(index, index+1, rangeProofs, true) - return &ShareWithProof{ - share, - &proof, + edsSize := len(dah.RowRoots) + isParity := shrIdx >= edsSize/2 || axisIdx >= edsSize/2 + namespace := share.ParitySharesNamespace + if !isParity { + namespace = share.GetNamespace(s.Share) } -} - -// Validate validates inclusion of the share under the given root CID. -func (s *ShareWithProof) Validate(root cid.Cid) bool { return s.Proof.VerifyInclusion( - sha256.New(), // TODO(@Wondertan): This should be defined somewhere globally - share.GetNamespace(s.Share).ToNMT(), - [][]byte{share.GetData(s.Share)}, - ipld.NamespacedSha256FromCID(root), + share.NewSHA256Hasher(), + namespace.ToNMT(), + [][]byte{s.Share}, + rootHash, ) } @@ -66,28 +68,54 @@ func (s *ShareWithProof) ShareWithProofToProto() *pb.Share { LeafHash: s.Proof.LeafHash(), IsMaxNamespaceIgnored: s.Proof.IsMaxNamespaceIDIgnored(), }, + ProofAxis: pb.Axis(s.Axis), } } -// GetProofsForShares fetches Merkle proofs for the given shares -// and returns the result as an array of ShareWithProof. -func GetProofsForShares( +// GetShareWithProof attempts to get a share with proof for the given share. It first tries to get a row proof +// and if that fails or proof is invalid, it tries to get a column proof. +func GetShareWithProof( ctx context.Context, bGetter blockservice.BlockGetter, - root cid.Cid, - shares [][]byte, -) ([]*ShareWithProof, error) { - proofs := make([]*ShareWithProof, len(shares)) - for index, share := range shares { - if share != nil { - proof, err := getProofsAt(ctx, bGetter, root, index, len(shares)) - if err != nil { - return nil, err - } - proofs[index] = proof + dah *share.Root, + share share.Share, + axisType rsmt2d.Axis, axisIdx, shrIdx int, +) (*ShareWithProof, error) { + if axisType == rsmt2d.Col { + axisIdx, shrIdx, axisType = shrIdx, axisIdx, rsmt2d.Row + } + width := len(dah.RowRoots) + // try row proofs + root := dah.RowRoots[axisIdx] + rootCid := ipld.MustCidFromNamespacedSha256(root) + proof, err := getProofsAt(ctx, bGetter, rootCid, shrIdx, width) + if err == nil { + shareWithProof := &ShareWithProof{ + Share: share, + Proof: &proof, + Axis: rsmt2d.Row, + } + if shareWithProof.Validate(dah, axisType, axisIdx, shrIdx) { + return shareWithProof, nil } } - return proofs, nil + + // try column proofs + root = dah.ColumnRoots[shrIdx] + rootCid = ipld.MustCidFromNamespacedSha256(root) + proof, err = getProofsAt(ctx, bGetter, rootCid, axisIdx, width) + if err != nil { + return nil, err + } + shareWithProof := &ShareWithProof{ + Share: share, + Proof: &proof, + Axis: rsmt2d.Col, + } + if shareWithProof.Validate(dah, axisType, axisIdx, shrIdx) { + return shareWithProof, nil + } + return nil, errors.New("failed to collect proof") } func getProofsAt( @@ -96,20 +124,20 @@ func getProofsAt( root cid.Cid, index, total int, -) (*ShareWithProof, error) { - proof := make([]cid.Cid, 0) - // TODO(@vgonkivs): Combine GetLeafData and GetProof in one function as the are traversing the same - // tree. Add options that will control what data will be fetched. - node, err := ipld.GetLeaf(ctx, bGetter, root, index, total) +) (nmt.Proof, error) { + proofPath := make([]cid.Cid, 0, int(math.Sqrt(float64(total)))) + proofPath, err := ipld.GetProof(ctx, bGetter, root, proofPath, index, total) if err != nil { - return nil, err + return nmt.Proof{}, err } - proof, err = ipld.GetProof(ctx, bGetter, root, proof, index, total) - if err != nil { - return nil, err + rangeProofs := make([][]byte, 0, len(proofPath)) + for i := len(proofPath) - 1; i >= 0; i-- { + node := ipld.NamespacedSha256FromCID(proofPath[i]) + rangeProofs = append(rangeProofs, node) } - return NewShareWithProof(index, node.RawData(), proof), nil + + return nmt.NewInclusionProof(index, index+1, rangeProofs, true), nil } func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { @@ -119,7 +147,11 @@ func ProtoToShare(protoShares []*pb.Share) []*ShareWithProof { continue } proof := ProtoToProof(share.Proof) - shares[i] = &ShareWithProof{share.Data, &proof} + shares[i] = &ShareWithProof{ + Share: share.Data, + Proof: &proof, + Axis: rsmt2d.Axis(share.ProofAxis), + } } return shares } @@ -132,3 +164,10 @@ func ProtoToProof(protoProof *nmt_pb.Proof) nmt.Proof { protoProof.IsMaxNamespaceIgnored, ) } + +func rootHashForCoordinates(r *share.Root, axisType rsmt2d.Axis, x, y int) []byte { + if axisType == rsmt2d.Row { + return r.RowRoots[y] + } + return r.ColumnRoots[x] +} diff --git a/share/eds/byzantine/share_proof_test.go b/share/eds/byzantine/share_proof_test.go index 9c883233ce..170ba591e2 100644 --- a/share/eds/byzantine/share_proof_test.go +++ b/share/eds/byzantine/share_proof_test.go @@ -2,21 +2,21 @@ package byzantine import ( "context" - "strconv" "testing" "time" - "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "github.com/celestiaorg/celestia-app/pkg/da" + "github.com/celestiaorg/rsmt2d" + "github.com/celestiaorg/celestia-node/share" "github.com/celestiaorg/celestia-node/share/ipld" "github.com/celestiaorg/celestia-node/share/sharetest" ) func TestGetProof(t *testing.T) { - const width = 4 + const width = 8 ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel() @@ -28,56 +28,36 @@ func TestGetProof(t *testing.T) { dah, err := da.NewDataAvailabilityHeader(in) require.NoError(t, err) - tests := []struct { - roots [][]byte - }{ - {dah.RowRoots}, - {dah.ColumnRoots}, - } - for i, tt := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - for _, root := range tt.roots { - rootCid := ipld.MustCidFromNamespacedSha256(root) - for index := 0; uint(index) < in.Width(); index++ { - proof := make([]cid.Cid, 0) - proof, err = ipld.GetProof(ctx, bServ, rootCid, proof, index, int(in.Width())) - require.NoError(t, err) - node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) - require.NoError(t, err) - inclusion := NewShareWithProof(index, node.RawData(), proof) - require.True(t, inclusion.Validate(rootCid)) + for _, proofType := range []rsmt2d.Axis{rsmt2d.Row, rsmt2d.Col} { + var roots [][]byte + switch proofType { + case rsmt2d.Row: + roots = dah.RowRoots + case rsmt2d.Col: + roots = dah.ColumnRoots + } + for axisIdx := 0; axisIdx < width*2; axisIdx++ { + rootCid := ipld.MustCidFromNamespacedSha256(roots[axisIdx]) + for shrIdx := 0; shrIdx < width*2; shrIdx++ { + proof, err := getProofsAt(ctx, bServ, rootCid, shrIdx, int(in.Width())) + require.NoError(t, err) + node, err := ipld.GetLeaf(ctx, bServ, rootCid, shrIdx, int(in.Width())) + require.NoError(t, err) + inclusion := &ShareWithProof{ + Share: share.GetData(node.RawData()), + Proof: &proof, + Axis: proofType, + } + require.True(t, inclusion.Validate(&dah, proofType, axisIdx, shrIdx)) + // swap axis indexes to test if validation still works against the orthogonal coordinate + switch proofType { + case rsmt2d.Row: + require.True(t, inclusion.Validate(&dah, rsmt2d.Col, shrIdx, axisIdx)) + case rsmt2d.Col: + require.True(t, inclusion.Validate(&dah, rsmt2d.Row, shrIdx, axisIdx)) } } - }) - } -} - -func TestGetProofs(t *testing.T) { - const width = 4 - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - defer cancel() - bServ := ipld.NewMemBlockservice() - - shares := sharetest.RandShares(t, width*width) - in, err := ipld.AddShares(ctx, shares, bServ) - require.NoError(t, err) - - dah, err := da.NewDataAvailabilityHeader(in) - require.NoError(t, err) - for _, root := range dah.ColumnRoots { - rootCid := ipld.MustCidFromNamespacedSha256(root) - data := make([][]byte, 0, in.Width()) - for index := 0; uint(index) < in.Width(); index++ { - node, err := ipld.GetLeaf(ctx, bServ, rootCid, index, int(in.Width())) - require.NoError(t, err) - data = append(data, node.RawData()[9:]) - } - - proves, err := GetProofsForShares(ctx, bServ, rootCid, data) - require.NoError(t, err) - for _, proof := range proves { - require.True(t, proof.Validate(rootCid)) } } } diff --git a/share/eds/eds.go b/share/eds/eds.go index e0433a1b6b..b8a332f275 100644 --- a/share/eds/eds.go +++ b/share/eds/eds.go @@ -3,7 +3,6 @@ package eds import ( "bytes" "context" - "crypto/sha256" "errors" "fmt" "io" @@ -68,7 +67,7 @@ func writeHeader(eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { // writeQuadrants reorders the shares to quadrant order and writes them to the CARv1 file. func writeQuadrants(eds *rsmt2d.ExtendedDataSquare, w io.Writer) error { - hasher := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, ipld.NMTIgnoreMaxNamespace) + hasher := nmt.NewNmtHasher(share.NewSHA256Hasher(), share.NamespaceSize, ipld.NMTIgnoreMaxNamespace) shares := quadrantOrder(eds) for _, share := range shares { leaf, err := hasher.HashLeaf(share) diff --git a/share/eds/retriever.go b/share/eds/retriever.go index b4a3e70158..989da376f7 100644 --- a/share/eds/retriever.go +++ b/share/eds/retriever.go @@ -91,7 +91,7 @@ func (r *Retriever) Retrieve(ctx context.Context, dah *da.DataAvailabilityHeader // nmt proofs computed during the session ses.close(false) span.RecordError(err) - return nil, byzantine.NewErrByzantine(ctx, r.bServ, dah, errByz) + return nil, byzantine.NewErrByzantine(ctx, r.bServ.Blockstore(), dah, errByz) } log.Warnw("not enough shares to reconstruct data square, requesting more...", "err", err) diff --git a/share/eds/retriever_test.go b/share/eds/retriever_test.go index 95da345d17..6bf6b45900 100644 --- a/share/eds/retriever_test.go +++ b/share/eds/retriever_test.go @@ -206,7 +206,7 @@ func BenchmarkNewErrByzantineData(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - err = byzantine.NewErrByzantine(ctx, bServ, h.DAH, errByz) + err = byzantine.NewErrByzantine(ctx, bServ.Blockstore(), h.DAH, errByz) require.NotNil(t, err) } }) diff --git a/share/getter.go b/share/getter.go index 3fcc93de33..363225f971 100644 --- a/share/getter.go +++ b/share/getter.go @@ -2,7 +2,6 @@ package share import ( "context" - "crypto/sha256" "errors" "fmt" @@ -90,7 +89,7 @@ func (row *NamespacedRow) verify(rowRoot []byte, namespace Namespace) bool { // verify namespace return row.Proof.VerifyNamespace( - sha256.New(), + NewSHA256Hasher(), namespace.ToNMT(), leaves, rowRoot, diff --git a/share/ipld/get_shares_test.go b/share/ipld/get_shares_test.go index c9796eb4be..2f5d630473 100644 --- a/share/ipld/get_shares_test.go +++ b/share/ipld/get_shares_test.go @@ -3,7 +3,6 @@ package ipld import ( "bytes" "context" - "crypto/sha256" "errors" mrand "math/rand" "sort" @@ -384,7 +383,7 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { // verify namespace verified := proof.VerifyNamespace( - sha256.New(), + share.NewSHA256Hasher(), namespace.ToNMT(), leaves, NamespacedSha256FromCID(rcid)) @@ -392,7 +391,7 @@ func TestGetSharesWithProofsByNamespace(t *testing.T) { // verify inclusion verified = proof.VerifyInclusion( - sha256.New(), + share.NewSHA256Hasher(), namespace.ToNMT(), rowShares, NamespacedSha256FromCID(rcid)) @@ -483,7 +482,7 @@ func assertNoRowContainsNID( // if no error returned, check absence proof foundAbsenceRows++ - verified := data.Proof().VerifyNamespace(sha256.New(), namespace.ToNMT(), nil, rowRoot) + verified := data.Proof().VerifyNamespace(share.NewSHA256Hasher(), namespace.ToNMT(), nil, rowRoot) require.True(t, verified) } diff --git a/share/ipld/nmt.go b/share/ipld/nmt.go index de662bb4b4..6140d569df 100644 --- a/share/ipld/nmt.go +++ b/share/ipld/nmt.go @@ -59,7 +59,7 @@ const ( func init() { // required for Bitswap to hash and verify inbound data correctly mhcore.Register(sha256NamespaceFlagged, func() hash.Hash { - nh := nmt.NewNmtHasher(sha256.New(), share.NamespaceSize, true) + nh := nmt.NewNmtHasher(share.NewSHA256Hasher(), share.NamespaceSize, true) nh.Reset() return nh }) diff --git a/share/share.go b/share/share.go index b98ffc94a0..298c3d46d4 100644 --- a/share/share.go +++ b/share/share.go @@ -2,8 +2,10 @@ package share import ( "bytes" + "crypto/sha256" "encoding/hex" "fmt" + "hash" "github.com/celestiaorg/celestia-app/pkg/appconsts" ) @@ -67,3 +69,8 @@ func MustDataHashFromString(datahash string) DataHash { } return dh } + +// NewSHA256Hasher returns a new instance of a SHA-256 hasher. +func NewSHA256Hasher() hash.Hash { + return sha256.New() +} diff --git a/state/core_access.go b/state/core_access.go index 0adc07fe8e..f19419885d 100644 --- a/state/core_access.go +++ b/state/core_access.go @@ -308,9 +308,9 @@ func (ca *CoreAccessor) SubmitPayForBlob( } if err != nil && errors.Is(err, sdkerrors.ErrNotFound) && !ca.granter.Empty() { - return response, errors.New("granter has revoked the grant") + return unsetTx(response), errors.New("granter has revoked the grant") } - return response, err + return unsetTx(response), err } return nil, fmt.Errorf("failed to submit blobs after %d attempts: %w", maxRetries, lastErr) } @@ -389,7 +389,7 @@ func (ca *CoreAccessor) SubmitTx(ctx context.Context, tx Tx) (*TxResponse, error if err != nil { return nil, err } - return txResp.TxResponse, nil + return unsetTx(txResp.TxResponse), nil } func (ca *CoreAccessor) SubmitTxWithBroadcastMode( @@ -401,7 +401,7 @@ func (ca *CoreAccessor) SubmitTxWithBroadcastMode( if err != nil { return nil, err } - return txResp.TxResponse, nil + return unsetTx(txResp.TxResponse), nil } func (ca *CoreAccessor) Transfer( @@ -429,8 +429,8 @@ func (ca *CoreAccessor) Transfer( return nil, fmt.Errorf("estimating gas: %w", err) } } - - return signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + return unsetTx(resp), err } func (ca *CoreAccessor) CancelUnbondingDelegation( @@ -460,7 +460,8 @@ func (ca *CoreAccessor) CancelUnbondingDelegation( } } - return signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + return unsetTx(resp), err } func (ca *CoreAccessor) BeginRedelegate( @@ -490,7 +491,8 @@ func (ca *CoreAccessor) BeginRedelegate( } } - return signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + return unsetTx(resp), err } func (ca *CoreAccessor) Undelegate( @@ -518,7 +520,8 @@ func (ca *CoreAccessor) Undelegate( return nil, fmt.Errorf("estimating gas: %w", err) } } - return signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + return unsetTx(resp), err } func (ca *CoreAccessor) Delegate( @@ -546,7 +549,8 @@ func (ca *CoreAccessor) Delegate( return nil, fmt.Errorf("estimating gas: %w", err) } } - return signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), user.SetFee(fee.Uint64())) + return unsetTx(resp), err } func (ca *CoreAccessor) QueryDelegation( @@ -609,7 +613,8 @@ func (ca *CoreAccessor) GrantFee( return nil, nil } - return signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), withFee(fee)) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{msg}, user.SetGasLimit(gasLim), withFee(fee)) + return unsetTx(resp), err } func (ca *CoreAccessor) RevokeGrantFee( @@ -626,7 +631,8 @@ func (ca *CoreAccessor) RevokeGrantFee( granter := signer.Address() msg := feegrant.NewMsgRevokeAllowance(granter, grantee) - return signer.SubmitTx(ctx, []sdktypes.Msg{&msg}, user.SetGasLimit(gasLim), withFee(fee)) + resp, err := signer.SubmitTx(ctx, []sdktypes.Msg{&msg}, user.SetGasLimit(gasLim), withFee(fee)) + return unsetTx(resp), err } func (ca *CoreAccessor) LastPayForBlob() int64 { @@ -701,3 +707,17 @@ func withFee(fee Int) user.TxOption { gasFee := sdktypes.NewCoins(sdktypes.NewCoin(app.BondDenom, fee)) return user.SetFeeAmount(gasFee) } + +// THIS IS A TEMPORARY SOLUTION!!! +// unsetTx helps to fix issue in TxResponse marshaling. Marshaling TxReponse +// fails because `TxResponse.Tx` is not empty but does not contain respective codec +// for encoding using the standard `json.MarshalJSON()`. It becomes empty when +// https://github.com/celestiaorg/celestia-core/issues/1281 will be merged. +// The `TxResponse.Tx` contains the transaction that is sent to the cosmos-sdk in the form +// in which it is processed there, so the user should not be aware of it. +func unsetTx(txResponse *TxResponse) *TxResponse { + if txResponse != nil && txResponse.Tx != nil { + txResponse.Tx = nil + } + return txResponse +}