From 079d6c347951e78751f1ab4c9fb70eec1301d287 Mon Sep 17 00:00:00 2001 From: Fangyu Gai Date: Fri, 11 Oct 2024 16:31:40 +0800 Subject: [PATCH 1/5] fix sorting non-determinism --- x/btcstaking/types/btcstaking.go | 9 ++++ x/btcstaking/types/btcstaking_test.go | 78 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 x/btcstaking/types/btcstaking_test.go diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 2914205c3..7d6518f5e 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -60,6 +60,15 @@ func SortFinalityProvidersWithZeroedVotingPower(fps []*FinalityProviderDistInfo) return true } + if iShouldBeZeroed && jShouldBeZeroed { + // Both have zeroed voting power, compare BTC public keys + return fps[i].BtcPk.MarshalHex() < fps[j].BtcPk.MarshalHex() + } + + if fps[i].TotalVotingPower == fps[j].TotalVotingPower { + return fps[i].BtcPk.MarshalHex() < fps[j].BtcPk.MarshalHex() + } + return fps[i].TotalVotingPower > fps[j].TotalVotingPower }) } diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go new file mode 100644 index 000000000..08ac15750 --- /dev/null +++ b/x/btcstaking/types/btcstaking_test.go @@ -0,0 +1,78 @@ +package types + +import ( + "testing" + + bbn "github.com/babylonlabs-io/babylon/types" + "github.com/stretchr/testify/assert" +) + +func TestSortFinalityProvidersWithZeroedVotingPower(t *testing.T) { + tests := []struct { + name string + fps []*FinalityProviderDistInfo + expected []*FinalityProviderDistInfo + }{ + { + name: "Sort by voting power", + fps: []*FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp2", TotalVotingPower: 200, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + }, + expected: []*FinalityProviderDistInfo{ + {Addr: "fp2", TotalVotingPower: 200, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + { + name: "Jailed and non-timestamped providers at the end", + fps: []*FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x04}}, + {Addr: "fp2", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp4", TotalVotingPower: 50, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + }, + expected: []*FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x04}}, + {Addr: "fp4", TotalVotingPower: 50, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp2", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + { + name: "Equal voting power, sort by BTC public key", + fps: []*FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp2", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + }, + expected: []*FinalityProviderDistInfo{ + {Addr: "fp2", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + { + name: "Zeroed voting power, sort by BTC public key", + fps: []*FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp2", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: true, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x02}}, + }, + expected: []*FinalityProviderDistInfo{ + {Addr: "fp2", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: true, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp1", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SortFinalityProvidersWithZeroedVotingPower(tt.fps) + assert.Equal(t, tt.expected, tt.fps, "Sorted slice should match expected order") + }) + } +} From d77be3f2860cb32a656f9f19e8e6a7b4bec7aa1b Mon Sep 17 00:00:00 2001 From: Fangyu Gai Date: Fri, 11 Oct 2024 16:38:54 +0800 Subject: [PATCH 2/5] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d9603be6..53059d158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### Bug fixes +* [#180](https://github.com/babylonlabs-io/babylon/pull/180) Non-determinism in + sorting finality providers in the voting power table * [#154](https://github.com/babylonlabs-io/babylon/pull/154) Fix "edit-finality-provider" cmd argument index ### Improvements From 3a2616341ca13921ba93a99dc664711dc4fcf139 Mon Sep 17 00:00:00 2001 From: Fangyu Gai Date: Fri, 11 Oct 2024 16:42:42 +0800 Subject: [PATCH 3/5] minor --- x/btcstaking/types/btcstaking.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x/btcstaking/types/btcstaking.go b/x/btcstaking/types/btcstaking.go index 7d6518f5e..354b27a53 100644 --- a/x/btcstaking/types/btcstaking.go +++ b/x/btcstaking/types/btcstaking.go @@ -60,13 +60,16 @@ func SortFinalityProvidersWithZeroedVotingPower(fps []*FinalityProviderDistInfo) return true } + iPkHex, jPkHex := fps[i].BtcPk.MarshalHex(), fps[j].BtcPk.MarshalHex() + if iShouldBeZeroed && jShouldBeZeroed { // Both have zeroed voting power, compare BTC public keys - return fps[i].BtcPk.MarshalHex() < fps[j].BtcPk.MarshalHex() + return iPkHex < jPkHex } + // both voting power the same, compare BTC public keys if fps[i].TotalVotingPower == fps[j].TotalVotingPower { - return fps[i].BtcPk.MarshalHex() < fps[j].BtcPk.MarshalHex() + return iPkHex < jPkHex } return fps[i].TotalVotingPower > fps[j].TotalVotingPower From 952c3ba9e1ae8283713cc58389a96ca7762fbab7 Mon Sep 17 00:00:00 2001 From: Fangyu Gai Date: Fri, 11 Oct 2024 17:05:20 +0800 Subject: [PATCH 4/5] change test to fuzz test --- x/btcstaking/types/btcstaking_test.go | 123 +++++++++++--------------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 08ac15750..7093e44e9 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -1,78 +1,63 @@ -package types +package types_test import ( + "math" + "math/rand" "testing" + "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" - "github.com/stretchr/testify/assert" + "github.com/babylonlabs-io/babylon/x/btcstaking/types" + + "github.com/stretchr/testify/require" ) -func TestSortFinalityProvidersWithZeroedVotingPower(t *testing.T) { - tests := []struct { - name string - fps []*FinalityProviderDistInfo - expected []*FinalityProviderDistInfo - }{ - { - name: "Sort by voting power", - fps: []*FinalityProviderDistInfo{ - {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, - {Addr: "fp2", TotalVotingPower: 200, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, - {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, - }, - expected: []*FinalityProviderDistInfo{ - {Addr: "fp2", TotalVotingPower: 200, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, - {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, - {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, - }, - }, - { - name: "Jailed and non-timestamped providers at the end", - fps: []*FinalityProviderDistInfo{ - {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x04}}, - {Addr: "fp2", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, - {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x03}}, - {Addr: "fp4", TotalVotingPower: 50, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, - }, - expected: []*FinalityProviderDistInfo{ - {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x04}}, - {Addr: "fp4", TotalVotingPower: 50, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, - {Addr: "fp2", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, - {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x03}}, - }, - }, - { - name: "Equal voting power, sort by BTC public key", - fps: []*FinalityProviderDistInfo{ - {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, - {Addr: "fp2", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, - {Addr: "fp3", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, - }, - expected: []*FinalityProviderDistInfo{ - {Addr: "fp2", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, - {Addr: "fp3", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, - {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, - }, - }, - { - name: "Zeroed voting power, sort by BTC public key", - fps: []*FinalityProviderDistInfo{ - {Addr: "fp1", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, - {Addr: "fp2", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x01}}, - {Addr: "fp3", TotalVotingPower: 100, IsJailed: true, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x02}}, - }, - expected: []*FinalityProviderDistInfo{ - {Addr: "fp2", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x01}}, - {Addr: "fp3", TotalVotingPower: 100, IsJailed: true, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x02}}, - {Addr: "fp1", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, - }, - }, - } +func FuzzSortingDeterminism(f *testing.F) { + datagen.AddRandomSeedsToFuzzer(f, 500) + f.Fuzz(func(t *testing.T, seed int64) { + r := rand.New(rand.NewSource(seed)) + max_vp := math.MaxUint32 + + vp0 := datagen.RandomInt(r, max_vp) + 10 + vp1 := datagen.RandomInt(r, max_vp) + 10 + vp2 := datagen.RandomInt(r, max_vp) + 10 + vp3 := datagen.RandomInt(r, max_vp) + 10 + vp4 := datagen.RandomInt(r, max_vp) + 10 + + fpsWithMeta := []*types.FinalityProviderDistInfo{ + {TotalVotingPower: vp0, IsJailed: false, IsTimestamped: true, Addr: "addr0", BtcPk: &bbn.BIP340PubKey{0x00}}, + {TotalVotingPower: vp1, IsJailed: false, IsTimestamped: true, Addr: "addr1", BtcPk: &bbn.BIP340PubKey{0x01}}, + {TotalVotingPower: vp2, IsJailed: false, IsTimestamped: true, Addr: "addr2", BtcPk: &bbn.BIP340PubKey{0x02}}, + {TotalVotingPower: vp3, IsJailed: false, IsTimestamped: true, Addr: "addr3", BtcPk: &bbn.BIP340PubKey{0x03}}, + {TotalVotingPower: vp4, IsJailed: false, IsTimestamped: true, Addr: "addr4", BtcPk: &bbn.BIP340PubKey{0x04}}, + } + jailedIdx1 := datagen.RandomInt(r, len(fpsWithMeta)) + jailedIdx2 := datagen.RandomIntOtherThan(r, int(jailedIdx1), len(fpsWithMeta)) + + fpsWithMeta[jailedIdx1].IsJailed = true + fpsWithMeta[jailedIdx1].IsTimestamped = false + fpsWithMeta[jailedIdx2].IsJailed = true + fpsWithMeta[jailedIdx2].IsTimestamped = false + + fpsWithMeta1 := []*types.FinalityProviderDistInfo{ + {TotalVotingPower: vp0, IsJailed: false, IsTimestamped: true, Addr: "addr0", BtcPk: &bbn.BIP340PubKey{0x00}}, + {TotalVotingPower: vp1, IsJailed: false, IsTimestamped: true, Addr: "addr1", BtcPk: &bbn.BIP340PubKey{0x01}}, + {TotalVotingPower: vp2, IsJailed: false, IsTimestamped: true, Addr: "addr2", BtcPk: &bbn.BIP340PubKey{0x02}}, + {TotalVotingPower: vp3, IsJailed: false, IsTimestamped: true, Addr: "addr3", BtcPk: &bbn.BIP340PubKey{0x03}}, + {TotalVotingPower: vp4, IsJailed: false, IsTimestamped: true, Addr: "addr4", BtcPk: &bbn.BIP340PubKey{0x04}}, + } + + fpsWithMeta1[jailedIdx1].IsJailed = true + fpsWithMeta1[jailedIdx1].IsTimestamped = false + fpsWithMeta1[jailedIdx2].IsJailed = true + fpsWithMeta1[jailedIdx2].IsTimestamped = false + + types.SortFinalityProvidersWithZeroedVotingPower(fpsWithMeta) + types.SortFinalityProvidersWithZeroedVotingPower(fpsWithMeta1) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - SortFinalityProvidersWithZeroedVotingPower(tt.fps) - assert.Equal(t, tt.expected, tt.fps, "Sorted slice should match expected order") - }) - } + for i := 0; i < len(fpsWithMeta); i++ { + // our lists should be sorted in same order + require.Equal(t, fpsWithMeta[i].Addr, fpsWithMeta1[i].Addr) + } + }) } From 4caf0bd4288d53c87ab62e0cda7d8b2d636724e5 Mon Sep 17 00:00:00 2001 From: Fangyu Gai Date: Fri, 11 Oct 2024 17:49:42 +0800 Subject: [PATCH 5/5] fix fuzz test --- x/btcstaking/types/btcstaking_test.go | 141 +++++++++++++++++++++----- 1 file changed, 114 insertions(+), 27 deletions(-) diff --git a/x/btcstaking/types/btcstaking_test.go b/x/btcstaking/types/btcstaking_test.go index 7093e44e9..e42e75693 100644 --- a/x/btcstaking/types/btcstaking_test.go +++ b/x/btcstaking/types/btcstaking_test.go @@ -1,63 +1,150 @@ package types_test import ( - "math" "math/rand" "testing" + "github.com/stretchr/testify/require" + "github.com/babylonlabs-io/babylon/testutil/datagen" bbn "github.com/babylonlabs-io/babylon/types" "github.com/babylonlabs-io/babylon/x/btcstaking/types" - - "github.com/stretchr/testify/require" ) +func TestSortFinalityProvidersWithZeroedVotingPower(t *testing.T) { + tests := []struct { + name string + fps []*types.FinalityProviderDistInfo + expected []*types.FinalityProviderDistInfo + }{ + { + name: "Sort by voting power", + fps: []*types.FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp2", TotalVotingPower: 200, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + }, + expected: []*types.FinalityProviderDistInfo{ + {Addr: "fp2", TotalVotingPower: 200, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + { + name: "Jailed and non-timestamped providers at the end", + fps: []*types.FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x04}}, + {Addr: "fp2", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp4", TotalVotingPower: 50, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + }, + expected: []*types.FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x04}}, + {Addr: "fp4", TotalVotingPower: 50, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp2", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp3", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + { + name: "Equal voting power, sort by BTC public key", + fps: []*types.FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp2", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + }, + expected: []*types.FinalityProviderDistInfo{ + {Addr: "fp2", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp1", TotalVotingPower: 100, IsJailed: false, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + { + name: "Zeroed voting power, sort by BTC public key", + fps: []*types.FinalityProviderDistInfo{ + {Addr: "fp1", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + {Addr: "fp2", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: true, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x02}}, + }, + expected: []*types.FinalityProviderDistInfo{ + {Addr: "fp2", TotalVotingPower: 150, IsJailed: false, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x01}}, + {Addr: "fp3", TotalVotingPower: 100, IsJailed: true, IsTimestamped: false, BtcPk: &bbn.BIP340PubKey{0x02}}, + {Addr: "fp1", TotalVotingPower: 200, IsJailed: true, IsTimestamped: true, BtcPk: &bbn.BIP340PubKey{0x03}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + types.SortFinalityProvidersWithZeroedVotingPower(tt.fps) + require.Equal(t, tt.expected, tt.fps, "Sorted slice should match expected order") + }) + } +} + +// FuzzSortingDeterminism tests the property of the sorting algorithm that the result should +// be deterministic func FuzzSortingDeterminism(f *testing.F) { - datagen.AddRandomSeedsToFuzzer(f, 500) + datagen.AddRandomSeedsToFuzzer(f, 1000) f.Fuzz(func(t *testing.T, seed int64) { r := rand.New(rand.NewSource(seed)) - max_vp := math.MaxUint32 + max_vp := 10000 vp0 := datagen.RandomInt(r, max_vp) + 10 - vp1 := datagen.RandomInt(r, max_vp) + 10 + vp1 := vp0 // this is for the case voting power is the same vp2 := datagen.RandomInt(r, max_vp) + 10 vp3 := datagen.RandomInt(r, max_vp) + 10 vp4 := datagen.RandomInt(r, max_vp) + 10 + pk1, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + pk2, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + pk3, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + pk4, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + pk5, err := datagen.GenRandomBIP340PubKey(r) + require.NoError(t, err) + fpsWithMeta := []*types.FinalityProviderDistInfo{ - {TotalVotingPower: vp0, IsJailed: false, IsTimestamped: true, Addr: "addr0", BtcPk: &bbn.BIP340PubKey{0x00}}, - {TotalVotingPower: vp1, IsJailed: false, IsTimestamped: true, Addr: "addr1", BtcPk: &bbn.BIP340PubKey{0x01}}, - {TotalVotingPower: vp2, IsJailed: false, IsTimestamped: true, Addr: "addr2", BtcPk: &bbn.BIP340PubKey{0x02}}, - {TotalVotingPower: vp3, IsJailed: false, IsTimestamped: true, Addr: "addr3", BtcPk: &bbn.BIP340PubKey{0x03}}, - {TotalVotingPower: vp4, IsJailed: false, IsTimestamped: true, Addr: "addr4", BtcPk: &bbn.BIP340PubKey{0x04}}, + {TotalVotingPower: vp0, IsJailed: false, IsTimestamped: true, Addr: "addr0", BtcPk: pk1}, + {TotalVotingPower: vp1, IsJailed: false, IsTimestamped: true, Addr: "addr1", BtcPk: pk2}, + {TotalVotingPower: vp2, IsJailed: false, IsTimestamped: true, Addr: "addr2", BtcPk: pk3}, + {TotalVotingPower: vp3, IsJailed: false, IsTimestamped: true, Addr: "addr3", BtcPk: pk4}, + {TotalVotingPower: vp4, IsJailed: false, IsTimestamped: true, Addr: "addr4", BtcPk: pk5}, } - jailedIdx1 := datagen.RandomInt(r, len(fpsWithMeta)) - jailedIdx2 := datagen.RandomIntOtherThan(r, int(jailedIdx1), len(fpsWithMeta)) + jailedIdx := datagen.RandomInt(r, len(fpsWithMeta)) + noTimestampedIdx := datagen.RandomIntOtherThan(r, int(jailedIdx), len(fpsWithMeta)) - fpsWithMeta[jailedIdx1].IsJailed = true - fpsWithMeta[jailedIdx1].IsTimestamped = false - fpsWithMeta[jailedIdx2].IsJailed = true - fpsWithMeta[jailedIdx2].IsTimestamped = false + fpsWithMeta[jailedIdx].IsJailed = true + fpsWithMeta[jailedIdx].IsTimestamped = false + fpsWithMeta[noTimestampedIdx].IsJailed = false + fpsWithMeta[noTimestampedIdx].IsTimestamped = false fpsWithMeta1 := []*types.FinalityProviderDistInfo{ - {TotalVotingPower: vp0, IsJailed: false, IsTimestamped: true, Addr: "addr0", BtcPk: &bbn.BIP340PubKey{0x00}}, - {TotalVotingPower: vp1, IsJailed: false, IsTimestamped: true, Addr: "addr1", BtcPk: &bbn.BIP340PubKey{0x01}}, - {TotalVotingPower: vp2, IsJailed: false, IsTimestamped: true, Addr: "addr2", BtcPk: &bbn.BIP340PubKey{0x02}}, - {TotalVotingPower: vp3, IsJailed: false, IsTimestamped: true, Addr: "addr3", BtcPk: &bbn.BIP340PubKey{0x03}}, - {TotalVotingPower: vp4, IsJailed: false, IsTimestamped: true, Addr: "addr4", BtcPk: &bbn.BIP340PubKey{0x04}}, + {TotalVotingPower: vp0, IsJailed: false, IsTimestamped: true, Addr: "addr0", BtcPk: pk1}, + {TotalVotingPower: vp1, IsJailed: false, IsTimestamped: true, Addr: "addr1", BtcPk: pk2}, + {TotalVotingPower: vp2, IsJailed: false, IsTimestamped: true, Addr: "addr2", BtcPk: pk3}, + {TotalVotingPower: vp3, IsJailed: false, IsTimestamped: true, Addr: "addr3", BtcPk: pk4}, + {TotalVotingPower: vp4, IsJailed: false, IsTimestamped: true, Addr: "addr4", BtcPk: pk5}, } - fpsWithMeta1[jailedIdx1].IsJailed = true - fpsWithMeta1[jailedIdx1].IsTimestamped = false - fpsWithMeta1[jailedIdx2].IsJailed = true - fpsWithMeta1[jailedIdx2].IsTimestamped = false + fpsWithMeta1[jailedIdx].IsJailed = true + fpsWithMeta1[jailedIdx].IsTimestamped = false + fpsWithMeta1[noTimestampedIdx].IsJailed = false + fpsWithMeta1[noTimestampedIdx].IsTimestamped = false + + // Shuffle the fpsWithMeta1 slice + r.Shuffle(len(fpsWithMeta1), func(i, j int) { + fpsWithMeta1[i], fpsWithMeta1[j] = fpsWithMeta1[j], fpsWithMeta1[i] + }) types.SortFinalityProvidersWithZeroedVotingPower(fpsWithMeta) types.SortFinalityProvidersWithZeroedVotingPower(fpsWithMeta1) for i := 0; i < len(fpsWithMeta); i++ { // our lists should be sorted in same order - require.Equal(t, fpsWithMeta[i].Addr, fpsWithMeta1[i].Addr) + require.Equal(t, fpsWithMeta[i].BtcPk.MarshalHex(), fpsWithMeta1[i].BtcPk.MarshalHex()) } }) }