Skip to content

Commit

Permalink
feat: create foundation for fraudulent block production (#1992)
Browse files Browse the repository at this point in the history
## Overview

creates a malicious package that allows for minimal changes to the rest
of the application while also keeping the malicious portion of the code
as separate as possible as to not accidentally trigger the malicious
logic.

part of #1953

## Checklist

- [x] New and updated code has appropriate documentation
- [x] New and updated code has new and/or updated testing
- [x] Required CI checks are passing
- [x] Visual proof for any user facing features like CLI or
documentation updates
- [ ] Linked issues closed with keywords

---------

Co-authored-by: Rootul P <[email protected]>
  • Loading branch information
evan-forbes and rootulp committed Jul 3, 2023
1 parent 9c1d220 commit 90a611b
Show file tree
Hide file tree
Showing 10 changed files with 548 additions and 27 deletions.
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

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

0 comments on commit 90a611b

Please sign in to comment.