github.com/celestiaorg/celestia-node@v0.15.0-beta.1/nodebuilder/tests/fraud_test.go (about)

     1  //go:build fraud || integration
     2  
     3  package tests
     4  
     5  import (
     6  	"context"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ipfs/go-datastore"
    11  	ds_sync "github.com/ipfs/go-datastore/sync"
    12  	"github.com/libp2p/go-libp2p/core/host"
    13  	"github.com/libp2p/go-libp2p/core/peer"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/tendermint/tendermint/types"
    16  	"go.uber.org/fx"
    17  
    18  	"github.com/celestiaorg/go-fraud"
    19  
    20  	"github.com/celestiaorg/celestia-node/header"
    21  	headerfraud "github.com/celestiaorg/celestia-node/header/headertest/fraud"
    22  	"github.com/celestiaorg/celestia-node/nodebuilder"
    23  	"github.com/celestiaorg/celestia-node/nodebuilder/core"
    24  	"github.com/celestiaorg/celestia-node/nodebuilder/node"
    25  	"github.com/celestiaorg/celestia-node/nodebuilder/tests/swamp"
    26  	"github.com/celestiaorg/celestia-node/share/eds"
    27  	"github.com/celestiaorg/celestia-node/share/eds/byzantine"
    28  )
    29  
    30  /*
    31  Test-Case: Full Node will propagate a fraud proof to the network, once ByzantineError will be received from sampling.
    32  Pre-Requisites:
    33  - CoreClient is started by swamp.
    34  Steps:
    35  1. Create a Bridge Node(BN) with broken extended header at height 10.
    36  2. Start a BN.
    37  3. Create a Full Node(FN) with a connection to BN as a trusted peer.
    38  4. Start a FN.
    39  5. Subscribe to a fraud proof and wait when it will be received.
    40  6. Check FN is not synced to 15.
    41  Note: 15 is not available because DASer/Syncer will be stopped
    42  before reaching this height due to receiving a fraud proof.
    43  Another note: this test disables share exchange to speed up test results.
    44  7. Spawn a Light Node(LN) in order to sync a BEFP.
    45  8. Ensure that the BEFP was received.
    46  9. Try to start a Full Node(FN) that contains a BEFP in its store.
    47  */
    48  func TestFraudProofHandling(t *testing.T) {
    49  	ctx, cancel := context.WithTimeout(context.Background(), swamp.DefaultTestTimeout)
    50  	t.Cleanup(cancel)
    51  
    52  	const (
    53  		blocks    = 15
    54  		blockSize = 4
    55  		blockTime = time.Second
    56  	)
    57  
    58  	sw := swamp.NewSwamp(t, swamp.WithBlockTime(blockTime))
    59  	fillDn := swamp.FillBlocks(ctx, sw.ClientContext, sw.Accounts, blockSize, blocks)
    60  	set, val := sw.Validators(t)
    61  	fMaker := headerfraud.NewFraudMaker(t, 10, []types.PrivValidator{val}, set)
    62  
    63  	storeCfg := eds.DefaultParameters()
    64  	ds := ds_sync.MutexWrap(datastore.NewMapDatastore())
    65  	edsStore, err := eds.NewStore(storeCfg, t.TempDir(), ds)
    66  	require.NoError(t, err)
    67  	require.NoError(t, edsStore.Start(ctx))
    68  	t.Cleanup(func() {
    69  		_ = edsStore.Stop(ctx)
    70  	})
    71  
    72  	cfg := nodebuilder.DefaultConfig(node.Bridge)
    73  	// 1.
    74  	bridge := sw.NewNodeWithConfig(
    75  		node.Bridge,
    76  		cfg,
    77  		core.WithHeaderConstructFn(fMaker.MakeExtendedHeader(16, edsStore)),
    78  		fx.Replace(edsStore),
    79  	)
    80  	// 2.
    81  	err = bridge.Start(ctx)
    82  	require.NoError(t, err)
    83  
    84  	// 3.
    85  	cfg = nodebuilder.DefaultConfig(node.Full)
    86  	addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(bridge.Host))
    87  	require.NoError(t, err)
    88  	cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String())
    89  	cfg.Share.UseShareExchange = false
    90  	store := nodebuilder.MockStore(t, cfg)
    91  	full := sw.NewNodeWithStore(node.Full, store)
    92  
    93  	// 4.
    94  	err = full.Start(ctx)
    95  	require.NoError(t, err)
    96  
    97  	fullClient := getAdminClient(ctx, full, t)
    98  
    99  	// 5.
   100  	subCtx, subCancel := context.WithCancel(ctx)
   101  	subscr, err := fullClient.Fraud.Subscribe(subCtx, byzantine.BadEncoding)
   102  	require.NoError(t, err)
   103  	select {
   104  	case p := <-subscr:
   105  		require.Equal(t, 10, int(p.Height()))
   106  		t.Log("Caught the proof....")
   107  		subCancel()
   108  	case <-ctx.Done():
   109  		subCancel()
   110  		t.Fatal("full node did not receive a fraud proof in time")
   111  	}
   112  
   113  	getCtx, getCancel := context.WithTimeout(ctx, time.Second)
   114  	proofs, err := fullClient.Fraud.Get(getCtx, byzantine.BadEncoding)
   115  	getCancel()
   116  
   117  	require.NoError(t, err)
   118  	require.Len(t, proofs, 1)
   119  	require.True(t, proofs[0].Type() == byzantine.BadEncoding)
   120  	// This is an obscure way to check if the Syncer was stopped.
   121  	// If we cannot get a height header within a timeframe it means the syncer was stopped
   122  	// FIXME: Eventually, this should be a check on service registry managing and keeping
   123  	//  lifecycles of each Module.
   124  	// 6.
   125  	// random height after befp.height
   126  	height := uint64(15)
   127  	// initial timeout is set to 5 sec, as we are targeting the height=15,
   128  	// blockTime=1 sec, expected befp.height=10
   129  	timeOut := blockTime * 5
   130  	// during befp validation the node can still receive headers and it mostly depends on
   131  	// the operating system or hardware(e.g. on macOS tests is working 100% time with a single
   132  	// height=15, and on the Linux VM sometimes the last height is 17-18). So, lets give a chance for
   133  	// our befp validator to check the fraud proof and stop the syncer.
   134  	for height < 20 {
   135  		syncCtx, syncCancel := context.WithTimeout(context.Background(), timeOut)
   136  		_, err = full.HeaderServ.WaitForHeight(syncCtx, height)
   137  		syncCancel()
   138  		if err != nil {
   139  			break
   140  		}
   141  		timeOut = blockTime
   142  		height++
   143  	}
   144  	require.ErrorIs(t, err, context.DeadlineExceeded)
   145  
   146  	// 7.
   147  	cfg = nodebuilder.DefaultConfig(node.Light)
   148  	cfg.Header.TrustedPeers = append(cfg.Header.TrustedPeers, addrs[0].String())
   149  	lnStore := nodebuilder.MockStore(t, cfg)
   150  	light := sw.NewNodeWithStore(node.Light, lnStore)
   151  	require.NoError(t, light.Start(ctx))
   152  	lightClient := getAdminClient(ctx, light, t)
   153  
   154  	// 8.
   155  	subCtx, subCancel = context.WithCancel(ctx)
   156  	subscr, err = lightClient.Fraud.Subscribe(subCtx, byzantine.BadEncoding)
   157  	require.NoError(t, err)
   158  	select {
   159  	case p := <-subscr:
   160  		require.Equal(t, 10, int(p.Height()))
   161  		subCancel()
   162  	case <-ctx.Done():
   163  		subCancel()
   164  		t.Fatal("light node did not receive a fraud proof in time")
   165  	}
   166  
   167  	// 9.
   168  	fN := sw.NewNodeWithStore(node.Full, store)
   169  	err = fN.Start(ctx)
   170  	var fpExist *fraud.ErrFraudExists[*header.ExtendedHeader]
   171  	require.ErrorAs(t, err, &fpExist)
   172  
   173  	sw.StopNode(ctx, bridge)
   174  	sw.StopNode(ctx, full)
   175  	sw.StopNode(ctx, light)
   176  	require.NoError(t, <-fillDn)
   177  }