|
@ -63,7 +63,7 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { |
|
|
// Check verification returns an error.
|
|
|
// Check verification returns an error.
|
|
|
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) |
|
|
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) |
|
|
if assert.Error(t, err) { |
|
|
if assert.Error(t, err) { |
|
|
assert.Equal(t, err, light.ErrLightClientAttack) |
|
|
|
|
|
|
|
|
assert.Equal(t, light.ErrLightClientAttack, err) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Check evidence was sent to both full nodes.
|
|
|
// Check evidence was sent to both full nodes.
|
|
@ -90,76 +90,86 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
func TestLightClientAttackEvidence_Equivocation(t *testing.T) { |
|
|
func TestLightClientAttackEvidence_Equivocation(t *testing.T) { |
|
|
// primary performs an equivocation attack
|
|
|
|
|
|
var ( |
|
|
|
|
|
latestHeight = int64(10) |
|
|
|
|
|
valSize = 5 |
|
|
|
|
|
divergenceHeight = int64(6) |
|
|
|
|
|
primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight) |
|
|
|
|
|
primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight) |
|
|
|
|
|
) |
|
|
|
|
|
// validators don't change in this network (however we still use a map just for convenience)
|
|
|
|
|
|
witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime) |
|
|
|
|
|
witness := mockp.New(chainID, witnessHeaders, witnessValidators) |
|
|
|
|
|
|
|
|
verificationOptions := map[string]light.Option{ |
|
|
|
|
|
"sequential": light.SequentialVerification(), |
|
|
|
|
|
"skipping": light.SkippingVerification(light.DefaultTrustLevel), |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
for height := int64(1); height <= latestHeight; height++ { |
|
|
|
|
|
if height < divergenceHeight { |
|
|
|
|
|
primaryHeaders[height] = witnessHeaders[height] |
|
|
|
|
|
|
|
|
for s, verificationOption := range verificationOptions { |
|
|
|
|
|
t.Log("==> verification", s) |
|
|
|
|
|
|
|
|
|
|
|
// primary performs an equivocation attack
|
|
|
|
|
|
var ( |
|
|
|
|
|
latestHeight = int64(10) |
|
|
|
|
|
valSize = 5 |
|
|
|
|
|
divergenceHeight = int64(6) |
|
|
|
|
|
primaryHeaders = make(map[int64]*types.SignedHeader, latestHeight) |
|
|
|
|
|
primaryValidators = make(map[int64]*types.ValidatorSet, latestHeight) |
|
|
|
|
|
) |
|
|
|
|
|
// validators don't change in this network (however we still use a map just for convenience)
|
|
|
|
|
|
witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight+2, valSize, 2, bTime) |
|
|
|
|
|
witness := mockp.New(chainID, witnessHeaders, witnessValidators) |
|
|
|
|
|
|
|
|
|
|
|
for height := int64(1); height <= latestHeight; height++ { |
|
|
|
|
|
if height < divergenceHeight { |
|
|
|
|
|
primaryHeaders[height] = witnessHeaders[height] |
|
|
|
|
|
primaryValidators[height] = witnessValidators[height] |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
// we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for
|
|
|
|
|
|
// a different block (which we do by adding txs)
|
|
|
|
|
|
primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height, |
|
|
|
|
|
bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")}, |
|
|
|
|
|
witnessValidators[height], witnessValidators[height+1], hash("app_hash"), |
|
|
|
|
|
hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1) |
|
|
primaryValidators[height] = witnessValidators[height] |
|
|
primaryValidators[height] = witnessValidators[height] |
|
|
continue |
|
|
|
|
|
} |
|
|
} |
|
|
// we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for
|
|
|
|
|
|
// a different block (which we do by adding txs)
|
|
|
|
|
|
primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height, |
|
|
|
|
|
bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")}, |
|
|
|
|
|
witnessValidators[height], witnessValidators[height+1], hash("app_hash"), |
|
|
|
|
|
hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1) |
|
|
|
|
|
primaryValidators[height] = witnessValidators[height] |
|
|
|
|
|
} |
|
|
|
|
|
primary := mockp.New(chainID, primaryHeaders, primaryValidators) |
|
|
|
|
|
|
|
|
primary := mockp.New(chainID, primaryHeaders, primaryValidators) |
|
|
|
|
|
|
|
|
c, err := light.NewClient( |
|
|
|
|
|
ctx, |
|
|
|
|
|
chainID, |
|
|
|
|
|
light.TrustOptions{ |
|
|
|
|
|
Period: 4 * time.Hour, |
|
|
|
|
|
Height: 1, |
|
|
|
|
|
Hash: primaryHeaders[1].Hash(), |
|
|
|
|
|
}, |
|
|
|
|
|
primary, |
|
|
|
|
|
[]provider.Provider{witness}, |
|
|
|
|
|
dbs.New(dbm.NewMemDB(), chainID), |
|
|
|
|
|
light.Logger(log.TestingLogger()), |
|
|
|
|
|
light.MaxRetryAttempts(1), |
|
|
|
|
|
) |
|
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
c, err := light.NewClient( |
|
|
|
|
|
ctx, |
|
|
|
|
|
chainID, |
|
|
|
|
|
light.TrustOptions{ |
|
|
|
|
|
Period: 4 * time.Hour, |
|
|
|
|
|
Height: 1, |
|
|
|
|
|
Hash: primaryHeaders[1].Hash(), |
|
|
|
|
|
}, |
|
|
|
|
|
primary, |
|
|
|
|
|
[]provider.Provider{witness}, |
|
|
|
|
|
dbs.New(dbm.NewMemDB(), chainID), |
|
|
|
|
|
light.Logger(log.TestingLogger()), |
|
|
|
|
|
light.MaxRetryAttempts(1), |
|
|
|
|
|
verificationOption, |
|
|
|
|
|
) |
|
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
// Check verification returns an error.
|
|
|
|
|
|
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) |
|
|
|
|
|
if assert.Error(t, err) { |
|
|
|
|
|
assert.Equal(t, err, light.ErrLightClientAttack) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Check verification returns an error.
|
|
|
|
|
|
_, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) |
|
|
|
|
|
if assert.Error(t, err) { |
|
|
|
|
|
assert.Equal(t, light.ErrLightClientAttack, err) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Check evidence was sent to both full nodes.
|
|
|
|
|
|
// Common height should be set to the height of the divergent header in the instance
|
|
|
|
|
|
// of an equivocation attack and the validator sets are the same as what the witness has
|
|
|
|
|
|
evAgainstPrimary := &types.LightClientAttackEvidence{ |
|
|
|
|
|
ConflictingBlock: &types.LightBlock{ |
|
|
|
|
|
SignedHeader: primaryHeaders[divergenceHeight], |
|
|
|
|
|
ValidatorSet: primaryValidators[divergenceHeight], |
|
|
|
|
|
}, |
|
|
|
|
|
CommonHeight: divergenceHeight, |
|
|
|
|
|
} |
|
|
|
|
|
assert.True(t, witness.HasEvidence(evAgainstPrimary)) |
|
|
|
|
|
|
|
|
// Check evidence was sent to both full nodes.
|
|
|
|
|
|
// Common height should be set to the height of the divergent header in the instance
|
|
|
|
|
|
// of an equivocation attack and the validator sets are the same as what the witness has
|
|
|
|
|
|
evAgainstPrimary := &types.LightClientAttackEvidence{ |
|
|
|
|
|
ConflictingBlock: &types.LightBlock{ |
|
|
|
|
|
SignedHeader: primaryHeaders[divergenceHeight], |
|
|
|
|
|
ValidatorSet: primaryValidators[divergenceHeight], |
|
|
|
|
|
}, |
|
|
|
|
|
CommonHeight: divergenceHeight, |
|
|
|
|
|
} |
|
|
|
|
|
assert.True(t, witness.HasEvidence(evAgainstPrimary)) |
|
|
|
|
|
|
|
|
evAgainstWitness := &types.LightClientAttackEvidence{ |
|
|
|
|
|
ConflictingBlock: &types.LightBlock{ |
|
|
|
|
|
SignedHeader: witnessHeaders[divergenceHeight], |
|
|
|
|
|
ValidatorSet: witnessValidators[divergenceHeight], |
|
|
|
|
|
}, |
|
|
|
|
|
CommonHeight: divergenceHeight, |
|
|
|
|
|
|
|
|
evAgainstWitness := &types.LightClientAttackEvidence{ |
|
|
|
|
|
ConflictingBlock: &types.LightBlock{ |
|
|
|
|
|
SignedHeader: witnessHeaders[divergenceHeight], |
|
|
|
|
|
ValidatorSet: witnessValidators[divergenceHeight], |
|
|
|
|
|
}, |
|
|
|
|
|
CommonHeight: divergenceHeight, |
|
|
|
|
|
} |
|
|
|
|
|
assert.True(t, primary.HasEvidence(evAgainstWitness)) |
|
|
} |
|
|
} |
|
|
assert.True(t, primary.HasEvidence(evAgainstWitness)) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 1. Different nodes therefore a divergent header is produced.
|
|
|
// 1. Different nodes therefore a divergent header is produced.
|
|
|