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

feat: create foundation for fraudulent block production #1992

Merged
merged 10 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/celestiaorg/celestia-app
go 1.20

require (
github.com/celestiaorg/nmt v0.16.0
github.com/celestiaorg/nmt v0.17.0
github.com/celestiaorg/quantum-gravity-bridge v1.3.0
github.com/ethereum/go-ethereum v1.12.0
github.com/gogo/protobuf v1.3.3
Expand Down
7 changes: 5 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13 h1:vaQKgaOm0w58JAvOgn2iDo
github.com/celestiaorg/cosmos-sdk v1.15.0-sdk-v0.46.13/go.mod h1:G9XkhOJZde36FH0kt/1ayg4ZaioZEQmmRfMa/zQig0I=
github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc=
github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA=
github.com/celestiaorg/nmt v0.16.0 h1:4CX6d1Uwf1C+tGcAWskPve0HCDTnI4Ey8ffjiDwcGH0=
github.com/celestiaorg/nmt v0.16.0/go.mod h1:GfwIvQPhUakn1modWxJ+rv8dUjJzuXg5H+MLFM1o7nY=
github.com/celestiaorg/nmt v0.17.0 h1:/k8YLwJvuHgT/jQ435zXKaDX811+sYEMXL4B/vYdSLU=
github.com/celestiaorg/nmt v0.17.0/go.mod h1:ZndCeAR4l9lxm7W51ouoyTo1cxhtFgK+4DpEIkxRA3A=
github.com/celestiaorg/quantum-gravity-bridge v1.3.0 h1:9zPIp7w1FWfkPnn16y3S4FpFLnQtS7rm81CUVcHEts0=
github.com/celestiaorg/quantum-gravity-bridge v1.3.0/go.mod h1:6WOajINTDEUXpSj5UZzod16UZ96ZVB/rFNKyM+Mt1gI=
github.com/celestiaorg/rsmt2d v0.9.0 h1:kon78I748ZqjNzI8OAqPN+2EImuZuanj/6gTh8brX3o=
Expand Down Expand Up @@ -987,8 +987,11 @@ github.com/tidwall/btree v1.5.0 h1:iV0yVY/frd7r6qGBXfEYs7DH0gTDgrKTrDjS7xt/IyQ=
github.com/tidwall/btree v1.5.0/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE=
github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
Expand Down
19 changes: 18 additions & 1 deletion pkg/wrapper/nmt_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/celestiaorg/celestia-app/pkg/appconsts"
appns "github.com/celestiaorg/celestia-app/pkg/namespace"
"github.com/celestiaorg/nmt"
"github.com/celestiaorg/nmt/namespace"
"github.com/celestiaorg/rsmt2d"
)

Expand All @@ -25,7 +26,7 @@ var (
type ErasuredNamespacedMerkleTree struct {
squareSize uint64 // note: this refers to the width of the original square before erasure-coded
options []nmt.Option
tree *nmt.NamespacedMerkleTree
tree Tree
// axisIndex is the index of the axis (row or column) that this tree is on. This is passed
// by rsmt2d and used to help determine which quadrant each leaf belongs to.
axisIndex uint64
Expand All @@ -37,6 +38,16 @@ type ErasuredNamespacedMerkleTree struct {
shareIndex uint64
}

// Tree is an interface that wraps the methods of the underlying
// NamespaceMerkleTree that are used by ErasuredNamespacedMerkleTree. This
// interface is mainly used for testing. It is not recommended to use this
// interface by implementing a different implementation.
type Tree interface {
Root() ([]byte, error)
Push(namespacedData namespace.PrefixedData) error
ProveRange(start, end int) (nmt.Proof, error)
}

// NewErasuredNamespacedMerkleTree creates a new ErasuredNamespacedMerkleTree
// with an underlying NMT of namespace size `appconsts.NamespaceSize` and with
// `ignoreMaxNamespace=true`. axisIndex is the index of the row or column that
Expand Down Expand Up @@ -127,3 +138,9 @@ func (w *ErasuredNamespacedMerkleTree) incrementShareIndex() {
func (w *ErasuredNamespacedMerkleTree) isQuadrantZero() bool {
return w.shareIndex < w.squareSize && w.axisIndex < w.squareSize
}

// SetTree sets the underlying tree to the provided tree. This is used for
// testing purposes only.
func (w *ErasuredNamespacedMerkleTree) SetTree(tree Tree) {
w.tree = tree
}
27 changes: 4 additions & 23 deletions pkg/wrapper/nmt_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
"github.com/celestiaorg/celestia-app/pkg/appconsts"
"github.com/celestiaorg/celestia-app/pkg/namespace"
appns "github.com/celestiaorg/celestia-app/pkg/namespace"
"github.com/celestiaorg/celestia-app/test/util/testfactory"
"github.com/celestiaorg/nmt"
nmtnamespace "github.com/celestiaorg/nmt/namespace"
"github.com/celestiaorg/rsmt2d"
"github.com/stretchr/testify/assert"
tmrand "github.com/tendermint/tendermint/libs/rand"
)

func TestPushErasuredNamespacedMerkleTree(t *testing.T) {
Expand Down Expand Up @@ -48,7 +48,7 @@ func TestPushErasuredNamespacedMerkleTree(t *testing.T) {
// to the second half of the tree.
func TestRootErasuredNamespacedMerkleTree(t *testing.T) {
size := 8
data := generateRandNamespacedRawData(size)
data := testfactory.GenerateRandNamespacedRawData(size)
nmtErasured := NewErasuredNamespacedMerkleTree(uint64(size), 0)
nmtStandard := nmt.New(sha256.New(), nmt.NamespaceIDSize(namespace.NamespaceSize), nmt.IgnoreMaxNamespace(true))

Expand Down Expand Up @@ -130,7 +130,7 @@ func TestErasureNamespacedMerkleTreePushErrors(t *testing.T) {
func TestComputeExtendedDataSquare(t *testing.T) {
squareSize := 4
// data for a 4X4 square
data := generateRandNamespacedRawData(squareSize * squareSize)
data := testfactory.GenerateRandNamespacedRawData(squareSize * squareSize)

_, err := rsmt2d.ComputeExtendedDataSquare(data, appconsts.DefaultCodec(), NewConstructor(uint64(squareSize)))
assert.NoError(t, err)
Expand All @@ -140,33 +140,14 @@ func TestComputeExtendedDataSquare(t *testing.T) {
// returns a slice that is twice as long as numLeaves because it returns the
// original data + erasured data.
func generateErasuredData(t *testing.T, numLeaves int, codec rsmt2d.Codec) [][]byte {
raw := generateRandNamespacedRawData(numLeaves)
raw := testfactory.GenerateRandNamespacedRawData(numLeaves)
erasuredData, err := codec.Encode(raw)
if err != nil {
t.Error(err)
}
return append(raw, erasuredData...)
}

// generateRandNamespacedRawData returns random data of length count. Each chunk
// of random data is of size shareSize and is prefixed with a random blob
// namespace.
func generateRandNamespacedRawData(count int) (result [][]byte) {
for i := 0; i < count; i++ {
rawData := tmrand.Bytes(appconsts.ShareSize)
namespace := appns.RandomBlobNamespace().Bytes()
copy(rawData, namespace)
result = append(result, rawData)
}

sortByteArrays(result)
return result
}

func sortByteArrays(src [][]byte) {
sort.Slice(src, func(i, j int) bool { return bytes.Compare(src[i], src[j]) < 0 })
}

// TestErasuredNamespacedMerkleTree_ProveRange checks that the proof returned by the ProveRange for all the shares within the erasured data is non-empty.
func TestErasuredNamespacedMerkleTree_ProveRange(t *testing.T) {
for sqaureSize := 1; sqaureSize <= 16; sqaureSize++ {
Expand Down
67 changes: 67 additions & 0 deletions test/util/malicious/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package malicious
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd prefer to call this package faulty for capturing general faulty behaviour but YMMV


import (
"io"

"github.com/celestiaorg/celestia-app/app"
"github.com/celestiaorg/celestia-app/app/encoding"
"github.com/cosmos/cosmos-sdk/baseapp"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
)

const (
// PrepareProposalHandlerKey is the key used to retrieve the PrepareProposal handler from the
// app options.
PrepareProposalHandlerKey = "prepare_proposal_handler"
)

type App struct {
*app.App
preparePropsoalHandler func(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal
}

func New(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
skipUpgradeHeights map[int64]bool,
homePath string,
invCheckPeriod uint,
encodingConfig encoding.Config,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *App {
goodApp := app.New(logger, db, traceStore, loadLatest, skipUpgradeHeights, homePath, invCheckPeriod, encodingConfig, appOpts, baseAppOptions...)
badApp := &App{App: goodApp}

// default to using the good app's handlers
badApp.SetPrepareProposalHandler(goodApp.PrepareProposal)

// override the handler if it is set in the app options
if prepareHander := appOpts.Get(PrepareProposalHandlerKey); prepareHander != nil {
badApp.SetPrepareProposalHandler(prepareHander.(func(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal))
}

return badApp
}

func (app *App) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
return app.preparePropsoalHandler(req)
}

// SetPrepareProposalHandler sets the PrepareProposal handler.
func (app *App) SetPrepareProposalHandler(handler func(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal) {
app.preparePropsoalHandler = handler
}

// ProcessProposal overwrites the default app's method to auto accept any
// proposal.
func (app *App) ProcessProposal(_ abci.RequestProcessProposal) (resp abci.ResponseProcessProposal) {
return abci.ResponseProcessProposal{
Result: abci.ResponseProcessProposal_ACCEPT,
}
}
66 changes: 66 additions & 0 deletions test/util/malicious/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package malicious

import (
"testing"

"github.com/celestiaorg/celestia-app/pkg/wrapper"
"github.com/celestiaorg/celestia-app/test/util/testfactory"
"github.com/celestiaorg/celestia-app/test/util/testnode"
"github.com/stretchr/testify/require"
tmrand "github.com/tendermint/tendermint/libs/rand"
)

// TestOutOfOrderNMT tests that the malicious NMT implementation is able to
// generate the same root as the ordered NMT implementation when the leaves are
// added in the same order and can generate roots when leaves are out of
// order.
func TestOutOfOrderNMT(t *testing.T) {
squareSize := uint64(64)
c := NewConstructor(squareSize)
goodConstructor := wrapper.NewConstructor(squareSize)

orderedTree := goodConstructor(0, 0)
maliciousOrderedTree := c(0, 0)
maliciousUnorderedTree := c(0, 0)
data := testfactory.GenerateRandNamespacedRawData(64)

// compare the roots generated by pushing ordered data
for _, d := range data {
err := orderedTree.Push(d)
require.NoError(t, err)
err = maliciousOrderedTree.Push(d)
require.NoError(t, err)
}

goodOrderedRoot, err := orderedTree.Root()
require.NoError(t, err)
malOrderedRoot, err := maliciousOrderedTree.Root()
require.NoError(t, err)
require.Equal(t, goodOrderedRoot, malOrderedRoot)

// test the new tree with unordered data
for i := range data {
j := tmrand.Intn(len(data))
data[i], data[j] = data[j], data[i]
}

for _, d := range data {
err := maliciousUnorderedTree.Push(d)
require.NoError(t, err)
}

root, err := maliciousUnorderedTree.Root()
require.NoError(t, err)
require.Len(t, root, 90) // two namespaces + 32 bytes of hash = 90 bytes
require.NotEqual(t, goodOrderedRoot, root) // quick sanity check to ensure the roots are different
}

func TestMaliciousNodeTestNode(t *testing.T) {
// TODO: flesh out this test further
cfg := testnode.DefaultConfig().
WithAppCreator(NewAppServer)

cctx, _, _ := testnode.NewNetwork(t, cfg)

require.NoError(t, cctx.WaitForNextBlock())
}
Loading