code.vegaprotocol.io/vega@v0.79.0/core/validators/heartbeat_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package validators 17 18 import ( 19 "context" 20 "errors" 21 "strconv" 22 "testing" 23 "time" 24 25 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 26 27 "github.com/stretchr/testify/require" 28 ) 29 30 func TestRecordHeartbeatResult(t *testing.T) { 31 top := getHBTestTopology(t) 32 tracker := top.validators["node1"].heartbeatTracker 33 34 for i := 0; i < 100; i++ { 35 if i%2 == 0 { 36 tracker.recordHeartbeatResult(true) 37 require.Equal(t, true, tracker.blockSigs[i%10]) 38 } else { 39 tracker.recordHeartbeatResult(false) 40 require.Equal(t, false, tracker.blockSigs[i%10]) 41 } 42 require.Equal(t, "", tracker.expectedNextHash) 43 require.Equal(t, time.Time{}, tracker.expectedNexthashSince) 44 } 45 } 46 47 func TestCheckAndExpireStaleHeartbeats(t *testing.T) { 48 top := getHBTestTopology(t) 49 top.epochSeq = 0 50 tt := &dummyTestTime{} 51 top.timeService = tt 52 53 now := time.Now() 54 nowPlus500 := now.Add(500 * time.Second) 55 tt.now = now 56 57 // no next hash - means we're not awaiting a heartbeat, nothing expired 58 top.checkAndExpireStaleHeartbeats() 59 require.Equal(t, 0, top.validators["node1"].heartbeatTracker.blockIndex) 60 61 top.validators["node1"].heartbeatTracker.expectedNextHash = "abcde" 62 top.validators["node1"].heartbeatTracker.expectedNexthashSince = now 63 top.checkAndExpireStaleHeartbeats() 64 require.Equal(t, 0, top.validators["node1"].heartbeatTracker.blockIndex) 65 66 // still not enough time passed 67 tt.now = nowPlus500 68 top.validators["node1"].heartbeatTracker.expectedNextHash = "abcde" 69 top.validators["node1"].heartbeatTracker.expectedNexthashSince = now 70 top.checkAndExpireStaleHeartbeats() 71 require.Equal(t, 0, top.validators["node1"].heartbeatTracker.blockIndex) 72 73 // enough time passed - expect invalidation 74 tt.now = nowPlus500.Add(1 * time.Second) 75 top.validators["node1"].heartbeatTracker.expectedNextHash = "abcde" 76 top.validators["node1"].heartbeatTracker.expectedNexthashSince = now 77 top.checkAndExpireStaleHeartbeats() 78 require.Equal(t, 1, top.validators["node1"].heartbeatTracker.blockIndex) 79 require.Equal(t, "", top.validators["node1"].heartbeatTracker.expectedNextHash) 80 81 // set up a new expected heartbeat and pretend the old one arrived again 82 cmd := &commandspb.ValidatorHeartbeat{ 83 NodeId: "node1", 84 Message: "abcde", 85 } 86 top.validators["node1"].heartbeatTracker.expectedNextHash = "abcdeagain" 87 top.validators["node1"].heartbeatTracker.expectedNexthashSince = now 88 require.Error(t, ErrHeartbeatHasExpired, top.ProcessValidatorHeartbeat(context.Background(), cmd, nil, nil)) 89 require.Equal(t, "abcdeagain", top.validators["node1"].heartbeatTracker.expectedNextHash) 90 } 91 92 func TestProcessValidatorHeartbeat(t *testing.T) { 93 topology := &Topology{} 94 topology.validators = map[string]*valState{} 95 cmd := &commandspb.ValidatorHeartbeat{} 96 cmd.NodeId = "node1" 97 98 // invalid node ID 99 require.Error(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, nil, nil)) 100 101 topology.validators["node1"] = &valState{ 102 heartbeatTracker: &validatorHeartbeatTracker{}, 103 } 104 105 for i := 0; i < 10; i++ { 106 topology.validators["node1"].heartbeatTracker.blockSigs[i] = true 107 } 108 109 // undecodable signature 110 cmd.VegaSignature = &commandspb.Signature{ 111 Value: "haha", 112 } 113 114 require.Error(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, nil, nil)) 115 require.Equal(t, 1, topology.validators["node1"].heartbeatTracker.blockIndex) 116 require.Equal(t, false, topology.validators["node1"].heartbeatTracker.blockSigs[0]) 117 118 cmd.VegaSignature.Value = "abcdef" 119 120 topology.validators["node1"].data.VegaPubKey = "fooo" 121 require.Error(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, nil, nil)) 122 require.Equal(t, 2, topology.validators["node1"].heartbeatTracker.blockIndex) 123 require.Equal(t, false, topology.validators["node1"].heartbeatTracker.blockSigs[1]) 124 125 topology.validators["node1"].data.VegaPubKey = "eeee" 126 rejectVega := func(message, signature, pubkey []byte) error { return errors.New("unverifiable vega signature") } 127 rejectEth := func(message, signature []byte, hexAddress string) error { 128 return errors.New("unverifiable eth signature") 129 } 130 acceptVega := func(message, signature, pubkey []byte) error { return nil } 131 acceptEth := func(message, signature []byte, hexAddress string) error { return nil } 132 133 // unverifiable vega signature 134 require.Error(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, rejectVega, nil)) 135 require.Equal(t, 3, topology.validators["node1"].heartbeatTracker.blockIndex) 136 require.Equal(t, false, topology.validators["node1"].heartbeatTracker.blockSigs[2]) 137 138 // undecodable eth signature 139 cmd.EthereumSignature = &commandspb.Signature{ 140 Value: "haha", 141 } 142 143 require.Error(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, acceptVega, nil)) 144 require.Equal(t, 4, topology.validators["node1"].heartbeatTracker.blockIndex) 145 require.Equal(t, false, topology.validators["node1"].heartbeatTracker.blockSigs[3]) 146 147 // rejected eth signature 148 cmd.EthereumSignature.Value = "ffff" 149 require.Error(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, acceptVega, rejectEth)) 150 require.Equal(t, 5, topology.validators["node1"].heartbeatTracker.blockIndex) 151 require.Equal(t, false, topology.validators["node1"].heartbeatTracker.blockSigs[4]) 152 153 // accepted eth signature 154 require.NoError(t, topology.ProcessValidatorHeartbeat(context.Background(), cmd, acceptVega, acceptEth)) 155 require.Equal(t, 6, topology.validators["node1"].heartbeatTracker.blockIndex) 156 require.Equal(t, true, topology.validators["node1"].heartbeatTracker.blockSigs[5]) 157 } 158 159 func TestGetNodeRequiringHB(t *testing.T) { 160 top := getHBTestTopology(t) 161 now := time.Now() 162 top.epochSeq = 1 163 164 tt := &dummyTestTime{} 165 top.timeService = tt 166 // initialise all to be not require resend for now 167 for _, vs := range top.validators { 168 vs.heartbeatTracker.expectedNexthashSince = now.Add(500 * time.Second) 169 } 170 171 top.validators["node1"].heartbeatTracker.expectedNexthashSince = now 172 top.validators["node2"].heartbeatTracker.expectedNexthashSince = now.Add(-200 * time.Second) 173 top.validators["node3"].data.FromEpoch = 2 174 top.validators["node3"].heartbeatTracker.expectedNexthashSince = now.Add(-300 * time.Second) 175 176 tt.now = now 177 res := top.getNodesRequiringHB() 178 require.Equal(t, 0, len(res)) 179 180 // move time by 801 seconds 181 tt.now = now.Add(801 * time.Second) 182 res = top.getNodesRequiringHB() 183 require.Equal(t, 1, len(res)) 184 require.Equal(t, "node2", res[0]) 185 186 top.epochSeq = 2 187 res = top.getNodesRequiringHB() 188 require.Equal(t, 2, len(res)) 189 require.Equal(t, "node2", res[0]) 190 require.Equal(t, "node3", res[1]) 191 192 // move time by 200 seconds 193 tt.now = now.Add(1001 * time.Second) 194 res = top.getNodesRequiringHB() 195 require.Equal(t, 3, len(res)) 196 require.Equal(t, "node1", res[0]) 197 require.Equal(t, "node2", res[1]) 198 require.Equal(t, "node3", res[2]) 199 } 200 201 func getHBTestTopology(t *testing.T) *Topology { 202 t.Helper() 203 204 topology := &Topology{} 205 topology.validators = map[string]*valState{} 206 topology.OnEpochLengthUpdate(context.Background(), 100000*time.Second) 207 for i := 0; i < 13; i++ { 208 index := strconv.Itoa(i) 209 topology.validators["node"+index] = &valState{ 210 data: ValidatorData{ 211 ID: "node" + index, 212 FromEpoch: 1, 213 }, 214 heartbeatTracker: &validatorHeartbeatTracker{}, 215 } 216 } 217 return topology 218 } 219 220 type dummyTestTime struct { 221 now time.Time 222 } 223 224 func (tt *dummyTestTime) GetTimeNow() time.Time { 225 return tt.now 226 }