github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/light/detector_test.go (about) 1 package light_test 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/require" 9 10 "github.com/lazyledger/lazyledger-core/libs/db/memdb" 11 "github.com/lazyledger/lazyledger-core/libs/log" 12 "github.com/lazyledger/lazyledger-core/light" 13 "github.com/lazyledger/lazyledger-core/light/provider" 14 mockp "github.com/lazyledger/lazyledger-core/light/provider/mock" 15 dbs "github.com/lazyledger/lazyledger-core/light/store/db" 16 "github.com/lazyledger/lazyledger-core/types" 17 ) 18 19 func TestLightClientAttackEvidence_Lunatic(t *testing.T) { 20 // primary performs a lunatic attack 21 var ( 22 latestHeight = int64(10) 23 valSize = 5 24 divergenceHeight = int64(6) 25 primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight) 26 primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight) 27 ) 28 29 witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime) 30 witness := mockp.New(chainID, witnessHeaders, witnessValidators) 31 forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) 32 forgedVals := forgedKeys.ToValidators(2, 0) 33 34 for height := int64(1); height <= latestHeight; height++ { 35 if height < divergenceHeight { 36 primaryHeaders[height] = witnessHeaders[height] 37 primaryValidators[height] = witnessValidators[height] 38 continue 39 } 40 primaryHeaders[height] = forgedKeys.GenSignedHeader(chainID, height, bTime.Add(time.Duration(height)*time.Minute), 41 nil, forgedVals, forgedVals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(forgedKeys)) 42 primaryValidators[height] = forgedVals 43 } 44 primary := mockp.New(chainID, primaryHeaders, primaryValidators) 45 46 c, err := light.NewClient( 47 ctx, 48 chainID, 49 light.TrustOptions{ 50 Period: 4 * time.Hour, 51 Height: 1, 52 Hash: primaryHeaders[1].Hash(), 53 }, 54 primary, 55 []provider.Provider{witness}, 56 dbs.New(memdb.NewDB(), chainID), 57 light.Logger(log.TestingLogger()), 58 light.MaxRetryAttempts(1), 59 ) 60 require.NoError(t, err) 61 62 // Check verification returns an error. 63 _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) 64 if assert.Error(t, err) { 65 assert.Equal(t, light.ErrLightClientAttack, err) 66 } 67 68 // Check evidence was sent to both full nodes. 69 evAgainstPrimary := &types.LightClientAttackEvidence{ 70 // after the divergence height the valset doesn't change so we expect the evidence to be for height 10 71 ConflictingBlock: &types.LightBlock{ 72 SignedHeader: primaryHeaders[10], 73 ValidatorSet: primaryValidators[10], 74 }, 75 CommonHeight: 4, 76 } 77 assert.True(t, witness.HasEvidence(evAgainstPrimary)) 78 79 evAgainstWitness := &types.LightClientAttackEvidence{ 80 // when forming evidence against witness we learn that the canonical chain continued to change validator sets 81 // hence the conflicting block is at 7 82 ConflictingBlock: &types.LightBlock{ 83 SignedHeader: witnessHeaders[7], 84 ValidatorSet: witnessValidators[7], 85 }, 86 CommonHeight: 4, 87 } 88 assert.True(t, primary.HasEvidence(evAgainstWitness)) 89 } 90 91 func TestLightClientAttackEvidence_Equivocation(t *testing.T) { 92 verificationOptions := map[string]light.Option{ 93 "sequential": light.SequentialVerification(), 94 "skipping": light.SkippingVerification(light.DefaultTrustLevel), 95 } 96 97 for s, verificationOption := range verificationOptions { 98 t.Log("==> verification", s) 99 100 // primary performs an equivocation attack 101 var ( 102 latestHeight = int64(10) 103 valSize = 5 104 divergenceHeight = int64(6) 105 primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight) 106 primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight) 107 ) 108 // validators don't change in this network (however we still use a map just for convenience) 109 witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime) 110 witness := mockp.New(chainID, witnessHeaders, witnessValidators) 111 112 for height := int64(1); height <= latestHeight; height++ { 113 if height < divergenceHeight { 114 primaryHeaders[height] = witnessHeaders[height] 115 primaryValidators[height] = witnessValidators[height] 116 continue 117 } 118 // we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for 119 // a different block (which we do by adding txs) 120 primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height, 121 bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")}, 122 witnessValidators[height], witnessValidators[height+1], hash("app_hash"), 123 hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1) 124 primaryValidators[height] = witnessValidators[height] 125 } 126 primary := mockp.New(chainID, primaryHeaders, primaryValidators) 127 128 c, err := light.NewClient( 129 ctx, 130 chainID, 131 light.TrustOptions{ 132 Period: 4 * time.Hour, 133 Height: 1, 134 Hash: primaryHeaders[1].Hash(), 135 }, 136 primary, 137 []provider.Provider{witness}, 138 dbs.New(memdb.NewDB(), chainID), 139 light.Logger(log.TestingLogger()), 140 light.MaxRetryAttempts(1), 141 verificationOption, 142 ) 143 require.NoError(t, err) 144 145 // Check verification returns an error. 146 _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) 147 if assert.Error(t, err) { 148 assert.Equal(t, light.ErrLightClientAttack, err) 149 } 150 151 // Check evidence was sent to both full nodes. 152 // Common height should be set to the height of the divergent header in the instance 153 // of an equivocation attack and the validator sets are the same as what the witness has 154 evAgainstPrimary := &types.LightClientAttackEvidence{ 155 ConflictingBlock: &types.LightBlock{ 156 SignedHeader: primaryHeaders[divergenceHeight], 157 ValidatorSet: primaryValidators[divergenceHeight], 158 }, 159 CommonHeight: divergenceHeight, 160 } 161 assert.True(t, witness.HasEvidence(evAgainstPrimary)) 162 163 evAgainstWitness := &types.LightClientAttackEvidence{ 164 ConflictingBlock: &types.LightBlock{ 165 SignedHeader: witnessHeaders[divergenceHeight], 166 ValidatorSet: witnessValidators[divergenceHeight], 167 }, 168 CommonHeight: divergenceHeight, 169 } 170 assert.True(t, primary.HasEvidence(evAgainstWitness)) 171 } 172 } 173 174 // 1. Different nodes therefore a divergent header is produced. 175 // => light client returns an error upon creation because primary and witness 176 // have a different view. 177 func TestClientDivergentTraces1(t *testing.T) { 178 primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime)) 179 firstBlock, err := primary.LightBlock(ctx, 1) 180 require.NoError(t, err) 181 witness := mockp.New(genMockNode(chainID, 10, 5, 2, bTime)) 182 183 _, err = light.NewClient( 184 ctx, 185 chainID, 186 light.TrustOptions{ 187 Height: 1, 188 Hash: firstBlock.Hash(), 189 Period: 4 * time.Hour, 190 }, 191 primary, 192 []provider.Provider{witness}, 193 dbs.New(memdb.NewDB(), chainID), 194 light.Logger(log.TestingLogger()), 195 light.MaxRetryAttempts(1), 196 ) 197 require.Error(t, err) 198 assert.Contains(t, err.Error(), "does not match primary") 199 } 200 201 // 2. Two out of three nodes don't respond but the third has a header that matches 202 // => verification should be successful and all the witnesses should remain 203 func TestClientDivergentTraces2(t *testing.T) { 204 primary := mockp.New(genMockNode(chainID, 10, 5, 2, bTime)) 205 firstBlock, err := primary.LightBlock(ctx, 1) 206 require.NoError(t, err) 207 c, err := light.NewClient( 208 ctx, 209 chainID, 210 light.TrustOptions{ 211 Height: 1, 212 Hash: firstBlock.Hash(), 213 Period: 4 * time.Hour, 214 }, 215 primary, 216 []provider.Provider{deadNode, deadNode, primary}, 217 dbs.New(memdb.NewDB(), chainID), 218 light.Logger(log.TestingLogger()), 219 light.MaxRetryAttempts(1), 220 ) 221 require.NoError(t, err) 222 223 _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) 224 assert.NoError(t, err) 225 assert.Equal(t, 3, len(c.Witnesses())) 226 } 227 228 // 3. witness has the same first header, but different second header 229 // => creation should succeed, but the verification should fail 230 func TestClientDivergentTraces3(t *testing.T) { 231 _, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime) 232 primary := mockp.New(chainID, primaryHeaders, primaryVals) 233 234 firstBlock, err := primary.LightBlock(ctx, 1) 235 require.NoError(t, err) 236 237 _, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime) 238 mockHeaders[1] = primaryHeaders[1] 239 mockVals[1] = primaryVals[1] 240 witness := mockp.New(chainID, mockHeaders, mockVals) 241 242 c, err := light.NewClient( 243 ctx, 244 chainID, 245 light.TrustOptions{ 246 Height: 1, 247 Hash: firstBlock.Hash(), 248 Period: 4 * time.Hour, 249 }, 250 primary, 251 []provider.Provider{witness}, 252 dbs.New(memdb.NewDB(), chainID), 253 light.Logger(log.TestingLogger()), 254 light.MaxRetryAttempts(1), 255 ) 256 require.NoError(t, err) 257 258 _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) 259 assert.Error(t, err) 260 assert.Equal(t, 0, len(c.Witnesses())) 261 }