ceremonyclient/node/consensus/time/data_time_reel_test.go
2024-10-12 11:55:17 -07:00

432 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package time_test
import (
"bytes"
"fmt"
"strings"
"sync"
"testing"
gotime "time"
"github.com/cloudflare/circl/sign/ed448"
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"source.quilibrium.com/quilibrium/monorepo/node/config"
"source.quilibrium.com/quilibrium/monorepo/node/consensus/time"
qcrypto "source.quilibrium.com/quilibrium/monorepo/node/crypto"
"source.quilibrium.com/quilibrium/monorepo/node/keys"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
"source.quilibrium.com/quilibrium/monorepo/node/store"
"source.quilibrium.com/quilibrium/monorepo/node/tries"
)
func generateTestProvers() (
keys.KeyManager,
[]peer.ID,
[][]byte,
[][]byte,
map[string]string,
*tries.RollingFrecencyCritbitTrie,
) {
keyManager := keys.NewInMemoryKeyManager()
peers := []peer.ID{}
pubKeys := [][]byte{}
privKeys := [][]byte{}
addrMap := map[string]string{}
for i := 0; i < 1000; i++ {
keyManager.CreateSigningKey(
fmt.Sprintf("test-key-%d", i),
keys.KeyTypeEd448,
)
k, err := keyManager.GetRawKey(fmt.Sprintf("test-key-%d", i))
if err != nil {
panic(err)
}
privKey, err := crypto.UnmarshalEd448PrivateKey([]byte(k.PrivateKey))
if err != nil {
panic(err)
}
privKeys = append(privKeys, []byte(k.PrivateKey))
pub := privKey.GetPublic()
id, err := peer.IDFromPublicKey(pub)
if err != nil {
panic(err)
}
peers = append(peers, id)
keyManager.CreateSigningKey(
fmt.Sprintf("proving-key-%d", i),
keys.KeyTypeEd448,
)
pk, err := keyManager.GetRawKey(fmt.Sprintf("proving-key-%d", i))
if err != nil {
panic(err)
}
pprivKey, err := crypto.UnmarshalEd448PrivateKey([]byte(pk.PrivateKey))
if err != nil {
panic(err)
}
ppub := pprivKey.GetPublic()
ppubKey, err := ppub.Raw()
if err != nil {
panic(err)
}
pubKeys = append(pubKeys, ppubKey)
}
proverTrie := &tries.RollingFrecencyCritbitTrie{}
for i, s := range pubKeys {
addr, err := poseidon.HashBytes(s)
if err != nil {
panic(err)
}
addrBytes := addr.Bytes()
addrBytes = append(make([]byte, 32-len(addrBytes)), addrBytes...)
proverTrie.Add(addrBytes, 0)
addrMap[string(addrBytes)] = fmt.Sprintf("proving-key-%d", i)
}
return keyManager,
peers,
pubKeys,
privKeys,
addrMap,
proverTrie
}
func TestDataTimeReel(t *testing.T) {
logger, _ := zap.NewDevelopment()
db := store.NewInMemKVDB()
clockStore := store.NewPebbleClockStore(db, logger)
prover := qcrypto.NewWesolowskiFrameProver(logger)
filter := "0000000000000000000000000000000000000000000000000000000000000000"
keyManager,
_,
pubKeys,
_,
addrMap,
proverTrie := generateTestProvers()
// We're going to set this up by churning 40 master frames so we don't
// have to zig zag on master and data frames to confirm data time reel
// behaviors
m := time.NewMasterTimeReel(
logger,
clockStore,
&config.EngineConfig{
Filter: filter,
GenesisSeed: strings.Repeat("00", 516),
Difficulty: 10,
},
prover,
)
err := m.Start()
assert.NoError(t, err)
frame, err := m.Head()
assert.NoError(t, err)
frames := []*protobufs.ClockFrame{}
wg := sync.WaitGroup{}
wg.Add(1)
frameCh := m.NewFrameCh()
go func() {
for i := 0; i < 40; i++ {
frames = append(frames, <-frameCh)
}
wg.Done()
}()
// in order
for i := int64(0); i < 40; i++ {
frame, err = prover.ProveMasterClockFrame(
frame,
i+1,
10,
[]*protobufs.InclusionAggregateProof{},
)
assert.NoError(t, err)
err := m.Insert(frame, false)
assert.NoError(t, err)
}
wg.Wait()
for i := 0; i < 40; i++ {
assert.NotNil(t, frames[i])
assert.Equal(t, frames[i].FrameNumber, uint64(i+1))
}
filterBytes := []byte{
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff,
}
// Ok, now onto the data time reel. We're going to test the following
// scenarios:
// 1. Z-dist optimal, in order
// 2. Z-dist optimal, out of order
// 3. 90% optimal, out of order
// 4. Malicious majority, out of order
d := time.NewDataTimeReel(
filterBytes,
logger,
clockStore,
&config.EngineConfig{
Filter: filter,
GenesisSeed: strings.Repeat("00", 516),
Difficulty: 10,
},
prover,
frames[0].Output,
&qcrypto.InclusionAggregateProof{
InclusionCommitments: []*qcrypto.InclusionCommitment{},
AggregateCommitment: []byte{},
Proof: []byte{},
},
pubKeys,
)
err = d.Start()
assert.NoError(t, err)
frame, err = d.Head()
assert.NoError(t, err)
dataFrames := []*protobufs.ClockFrame{}
datawg := sync.WaitGroup{}
datawg.Add(1)
dataFrameCh := d.NewFrameCh()
targetFrameParentSelector := []byte{}
go func() {
for {
frame := <-dataFrameCh
dataFrames = append(dataFrames, frame)
if frame.FrameNumber == 40 && bytes.Equal(
frame.ParentSelector,
targetFrameParentSelector,
) {
break
}
}
datawg.Done()
}()
// 1. z-dist optimal proof submission is strictly master-frame evoked leader
for i := int64(0); i < 10; i++ {
masterSelector, err := frames[i].GetSelector()
assert.NoError(t, err)
proverSelection := proverTrie.FindNearest(
masterSelector.FillBytes(make([]byte, 32)),
)
optimalSigner, _ := keyManager.GetSigningKey(
addrMap[string(proverSelection.External.Key)],
)
frame, err = prover.ProveDataClockFrame(
frame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
optimalSigner,
i+1,
10,
)
d.Insert(frame, false)
}
// 2. z-dist optimal, out of order proof submission is strictly master-frame
// evoked leader, but arrived completely backwards
insertFrames := []*protobufs.ClockFrame{}
for i := int64(10); i < 20; i++ {
masterSelector, err := frames[i].GetSelector()
assert.NoError(t, err)
proverSelection := proverTrie.FindNearest(
masterSelector.FillBytes(make([]byte, 32)),
)
optimalSigner, _ := keyManager.GetSigningKey(
addrMap[string(proverSelection.External.Key)],
)
frame, err = prover.ProveDataClockFrame(
frame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
optimalSigner,
i+1,
10,
)
insertFrames = append(insertFrames, frame)
}
for i := 9; i >= 0; i-- {
err := d.Insert(insertFrames[i], false)
assert.NoError(t, err)
}
// 3. 90% optimal, out of order
insertFrames = []*protobufs.ClockFrame{}
for i := int64(20); i < 25; i++ {
masterSelector, err := frames[i].GetSelector()
assert.NoError(t, err)
proverSelection := proverTrie.FindNearest(
masterSelector.FillBytes(make([]byte, 32)),
)
optimalSigner, _ := keyManager.GetSigningKey(
addrMap[string(proverSelection.External.Key)],
)
frame, err = prover.ProveDataClockFrame(
frame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
optimalSigner,
i+1,
10,
)
d.Insert(frame, false)
}
masterSelector, err := frames[25].GetSelector()
assert.NoError(t, err)
proverSelections := proverTrie.FindNearestAndApproximateNeighbors(
masterSelector.FillBytes(make([]byte, 32)),
)
suboptimalSigner2, _ := keyManager.GetSigningKey(
addrMap[string(proverSelections[2].External.Key)],
)
// What we're trying to simulate: consensus heads progressed on a slightly
// less optimal prover.
frame, err = prover.ProveDataClockFrame(
frame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
suboptimalSigner2,
26,
10,
)
insertFrames = append(insertFrames, frame)
for i := int64(26); i < 30; i++ {
masterSelector, err := frames[i].GetSelector()
assert.NoError(t, err)
proverSelection := proverTrie.FindNearest(
masterSelector.FillBytes(make([]byte, 32)),
)
optimalSigner, _ := keyManager.GetSigningKey(
addrMap[string(proverSelection.External.Key)],
)
frame, err = prover.ProveDataClockFrame(
frame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
optimalSigner,
i+1,
10,
)
insertFrames = append(insertFrames, frame)
}
for i := 4; i >= 0; i-- {
err := d.Insert(insertFrames[i], false)
assert.NoError(t, err)
}
// 4. Malicious majority, out of order handle a suppressive majority and
// force consensus on the lowest distance sub-tree:
insertFrames = []*protobufs.ClockFrame{}
conflictFrames := []*protobufs.ClockFrame{}
optimalKeySet := [][]byte{}
suppressedFrame := frame
for i := int64(30); i < 40; i++ {
masterSelector, err := frames[i].GetSelector()
assert.NoError(t, err)
proverSelections := proverTrie.FindNearestAndApproximateNeighbors(
masterSelector.FillBytes(make([]byte, 32)),
)
optimalSigner, _ := keyManager.GetSigningKey(
addrMap[string(proverSelections[0].External.Key)],
)
suboptimalSigner2, _ := keyManager.GetSigningKey(
addrMap[string(proverSelections[2].External.Key)],
)
optimalKeySet = append(optimalKeySet, []byte(
(optimalSigner.Public()).(ed448.PublicKey),
))
// What we're trying to simulate: the majority is intentionally ignoring
// the most optimal signer
suppressedFrame, err = prover.ProveDataClockFrame(
suppressedFrame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
optimalSigner,
i+1,
10,
)
insertFrames = append(insertFrames, suppressedFrame)
if i == 39 {
targetFrameParentSelector = suppressedFrame.ParentSelector
}
frame, err = prover.ProveDataClockFrame(
frame,
[][]byte{},
[]*protobufs.InclusionAggregateProof{},
suboptimalSigner2,
i+1,
10,
)
conflictFrames = append(conflictFrames, frame)
}
for i := 9; i >= 0; i-- {
err := d.Insert(conflictFrames[i], false)
// force linear ordering
gotime.Sleep(1 * gotime.Second)
assert.NoError(t, err)
}
// Someone is honest, but running backwards:
for i := 9; i >= 0; i-- {
err := d.Insert(insertFrames[i], false)
gotime.Sleep(1 * gotime.Second)
assert.NoError(t, err)
}
datawg.Wait()
assert.Equal(t, uint64(40), dataFrames[len(dataFrames)-1].FrameNumber)
assert.Equal(
t,
optimalKeySet[len(optimalKeySet)-1],
dataFrames[len(dataFrames)-1].GetPublicKeySignatureEd448().PublicKey.KeyValue,
)
}