func checkNoNewEnemy(ctx context.Context, t testing.TB, spicedb spice.Cluster, candidateCount int) (*bool, int) {
	var attempts int
	for {
		attempts++
		directs, excludes := generateTuples(1)
		r1, err := spicedb[0].Client().V0().ACL().Write(testCtx, &v0.WriteRequest{
			Updates: []*v0.RelationTupleUpdate{excludes[0]},
		})
		if err != nil {
			t.Log(err)
			continue
		}
		r2, err := spicedb[1].Client().V0().ACL().Write(testCtx, &v0.WriteRequest{
			Updates: []*v0.RelationTupleUpdate{directs[0]},
		})
		if err != nil {
			t.Log(err)
			continue
		}
		canHas, err := spicedb[2].Client().V0().ACL().Check(context.Background(), &v0.CheckRequest{
			TestUserset: &v0.ObjectAndRelation{
				Namespace: "resource",
				ObjectId:  "thegoods",
				Relation:  "allowed",
			},
			User:       directs[0].Tuple.GetUser(),
			AtRevision: r2.GetRevision(),
		})
		if err != nil {
			t.Log(err)
			continue
		}
		if canHas.IsMember {
			t.Log("service is subject to the new enemy problem")
		}
		analyzeCalls(os.Stdout, r1.GetRevision(), r2.GetRevision())
		if canHas.IsMember {
			t.Log("service is subject to the new enemy problem")
			protected := false
			return &protected, attempts
		}
		if candidateCount > 0 && attempts >= candidateCount {
			t.Log(candidateCount, "(would be) causal reversals with no new enemy detected")
			protected := true
			return &protected, attempts
		}
		select {
		case <-ctx.Done():
			return nil, attempts
		default:
			continue
		}
	}
}
checkNoNewEnemy returns true if the service is protected, false if it is vulnerable, and nil if we couldn't determine
package newenemy
import (
	"context"
	"fmt"
	"io"
	"math"
	"math/rand"
	"os"
	"testing"
	"time"
	v0 "github.com/authzed/authzed-go/proto/authzed/api/v0"
	"github.com/authzed/authzed-go/proto/authzed/api/v1alpha1"
	"github.com/authzed/spicedb/pkg/zookie"
	"github.com/stretchr/testify/require"
	"github.com/authzed/spicedb/e2e"
	"github.com/authzed/spicedb/e2e/cockroach"
	"github.com/authzed/spicedb/e2e/spice"
)
const schema = `
definition user {}
definition resource {
	relation direct: user
	relation excluded: user
	permission allowed = direct - excluded
}
`
var testCtx context.Context
func TestMain(m *testing.M) {
	var cancel context.CancelFunc
	testCtx, cancel = context.WithCancel(e2e.Context())
	code := m.Run()
	cancel()
	os.Exit(code)
}
const (
	createDb       = "CREATE DATABASE %s;"
	setSmallRanges = "ALTER DATABASE %s CONFIGURE ZONE USING range_min_bytes = 0, range_max_bytes = 65536, num_replicas = 1, gc.ttlseconds = 5;"
	dbName         = "spicedbnetest"
)
func startCluster(ctx context.Context, t testing.TB) cockroach.Cluster {
	require := require.New(t)
	fmt.Println("starting cockroach...")
	crdbCluster := cockroach.NewCluster(3)
	for _, c := range crdbCluster {
		require.NoError(c.Start(ctx))
	}
	fmt.Println("initializing crdb...")
	crdbCluster.Init(ctx, os.Stdout, os.Stdout)
	require.NoError(crdbCluster.SQL(ctx, os.Stdout, os.Stdout,
		fmt.Sprintf(createDb, dbName),
	))
	fmt.Println("migrating...")
	require.NoError(spice.MigrateHead(ctx, "cockroachdb", crdbCluster[0].ConnectionString(dbName)))
	fmt.Println("attempting to connect...")
	require.NoError(crdbCluster[2].Connect(ctx, os.Stdout, dbName))
	return crdbCluster
}
func TestNoNewEnemy(t *testing.T) {
	require := require.New(t)
	ctx, cancel := context.WithCancel(testCtx)
	defer cancel()
	crdb := startCluster(ctx, t)
	t.Log("starting vulnerable spicedb...")
	vulnerableSpiceDb := spice.NewClusterFromCockroachCluster(crdb, spice.WithDbName(dbName))
	require.NoError(vulnerableSpiceDb.Start(ctx, os.Stdout, "vulnerable",
		"--datastore-tx-overlap-strategy=insecure"))
	require.NoError(vulnerableSpiceDb.Connect(ctx, os.Stdout))
	t.Log("start protected spicedb cluster")
	protectedSpiceDb := spice.NewClusterFromCockroachCluster(crdb,
		spice.WithGrpcPort(50061),
		spice.WithInternalPort(50062),
		spice.WithHttpPort(8444),
		spice.WithMetricsPort(9100),
		spice.WithDashboardPort(8100),
		spice.WithDbName(dbName))
	require.NoError(protectedSpiceDb.Start(ctx, os.Stdout, "protected"))
	require.NoError(protectedSpiceDb.Connect(ctx, os.Stdout))
	t.Log("configure small ranges, single replicas, short ttl")
	require.NoError(crdb.SQL(ctx, os.Stdout, os.Stdout,
		fmt.Sprintf(setSmallRanges, dbName),
	))
	t.Log("modifying time")
	timeDelay := 100 * time.Millisecond
	require.NoError(crdb.TimeDelay(ctx, e2e.MustFile(ctx, t, "timeattack.log"), 1, -1*timeDelay))
	t.Log("modifying network")
	networkDelay := timeDelay / 2
	require.NoError(crdb.NetworkDelay(ctx, e2e.MustFile(ctx, t, "netattack.log"), 1, networkDelay))
	t.Log("create initial test schema")
	require.NoError(initSchema(vulnerableSpiceDb[0].Client().V1Alpha1().Schema()))
	t.Log("filling with data to span multiple ranges")
	rand.Seed(time.Now().UnixNano())
	fill(require, vulnerableSpiceDb[0].Client().V0().ACL(), 4000, 100)
	const sampleSize = 5
	samples := make([]int, sampleSize)
	for i := 0; i < sampleSize; i++ {
		t.Log(i, "check vulnerability with mitigations disabled")
		checkCtx, checkCancel := context.WithTimeout(ctx, 5*time.Minute)
		protected, attempts := checkNoNewEnemy(checkCtx, t, vulnerableSpiceDb, -1)
		require.NotNil(protected, "unable to determine if spicedb displays newenemy when mitigations are disabled within the time limit")
		require.False(*protected)
		checkCancel()
		t.Logf("%d - determined spicedb vulnerable in %d attempts", i, attempts)
		samples[i] = attempts
	}
	mean, M2 := 0.0, 0.0
	for n, x := range samples {
		delta := float64(x) - mean
		mean += delta / float64(n+1)
		M2 += delta * (float64(x) - mean)
	}
	variance := M2 / float64(sampleSize-1)
	stddev := math.Sqrt(variance)
	samplestddev := stddev / math.Sqrt(float64(sampleSize))
	iterations := int(math.Ceil(3*stddev*samplestddev + mean))
	t.Logf("check spicedb is protected after %d attempts", iterations)
	checkCtx, checkCancel := context.WithTimeout(ctx, time.Duration(iterations)*(networkDelay+timeDelay)*6)
	protected, _ := checkNoNewEnemy(checkCtx, t, protectedSpiceDb, iterations)
	require.NotNil(protected, "unable to determine if spicedb is protected within the time limit")
	require.True(*protected, "protection is enabled, but newenemy detected")
	checkCancel()
}