mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-05 04:55:18 +00:00
light: update initialization description (#320)
This commit is contained in:
@@ -634,4 +634,4 @@ func VerifyAndDetect (lightStore LightStore, targetHeight Height)
|
||||
- an attack is detected
|
||||
- [LC-DATA-PEERLIST-INV.1] is violated
|
||||
|
||||
----
|
||||
----
|
||||
131
spec/light-client/supervisor/supervisor_002_draft.md
Normal file
131
spec/light-client/supervisor/supervisor_002_draft.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Draft of Light Client Supervisor for discussion
|
||||
|
||||
## Modification to the initialization
|
||||
|
||||
The lightclient is initialized with LCInitData
|
||||
|
||||
### **[LC-DATA-INIT.2]**
|
||||
|
||||
```go
|
||||
type LCInitData struct {
|
||||
TrustedBlock LightBlock
|
||||
Genesis GenesisDoc
|
||||
TrustedHash []byte
|
||||
TrustedHeight int64
|
||||
}
|
||||
```
|
||||
|
||||
where only one of the components must be provided. `GenesisDoc` is
|
||||
defined in the [Tendermint
|
||||
Types](https://github.com/tendermint/tendermint/blob/master/types/genesis.go).
|
||||
|
||||
|
||||
### Initialization
|
||||
|
||||
The light client is based on subjective initialization. It has to
|
||||
trust the initial data given to it by the user. It cannot perform any
|
||||
detection of an attack yet instead requires an initial point of trust.
|
||||
There are three forms of initial data which are used to obtain the
|
||||
first trusted block:
|
||||
|
||||
- A trusted block from a prior initialization
|
||||
- A trusted height and hash
|
||||
- A genesis file
|
||||
|
||||
The golang light client implementation checks this initial data in that
|
||||
order; first attempting to find a trusted block from the trusted store,
|
||||
then acquiring a light block from the primary at the trusted height and matching
|
||||
the hash, or finally checking for a genesis file to verify the initial header.
|
||||
|
||||
The light client doesn't need to check if the trusted block is within the
|
||||
trusted period because it already trusts it, however, if the light block is
|
||||
outside the trust period, there is a higher chance the light client won't be
|
||||
able to verify anything.
|
||||
|
||||
Cross-checking this trusted block with providers upon initialization is helpful
|
||||
for ensuring that the node is responsive and correctly configured but does not
|
||||
increase trust since proving a conflicting block is a
|
||||
[light client attack](https://github.com/tendermint/spec/blob/master/spec/light-client/detection/detection_003_reviewed.md#tmbc-lc-attack1)
|
||||
and not just a [bogus](https://github.com/tendermint/spec/blob/master/spec/light-client/detection/detection_003_reviewed.md#tmbc-bogus1) block could result in
|
||||
performing backwards verification beyond the trusted period, thus a fruitless
|
||||
endeavour.
|
||||
|
||||
However, with the notion of it's better to fail earlier than later, the golang
|
||||
light client implementation will perform a consistency check on all providers
|
||||
and will error if one returns a different header, allowing the user
|
||||
the opportunity to reinitialize.
|
||||
|
||||
#### **[LC-FUNC-INIT.2]:**
|
||||
|
||||
```go
|
||||
func InitLightClient(initData LCInitData) (LightStore, Error) {
|
||||
var initialBlock LightBlock
|
||||
|
||||
switch {
|
||||
case LCInitData.TrustedBlock != nil:
|
||||
// we trust the block from a prior initialization
|
||||
initialBlock = LCInitData.TrustedBlock
|
||||
|
||||
case LCInitData.TrustedHash != nil:
|
||||
untrustedBlock := FetchLightBlock(PeerList.Primary(), LCInitData.TrustedHeight)
|
||||
|
||||
|
||||
// verify that the hashes match
|
||||
if untrustedBlock.Hash() != LCInitData.TrustedHash {
|
||||
return nil, Error("Primary returned block with different hash")
|
||||
}
|
||||
// after checking the hash we now trust the block
|
||||
initialBlock = untrustedBlock
|
||||
}
|
||||
case LCInitData.Genesis != nil:
|
||||
untrustedBlock := FetchLightBlock(PeerList.Primary(), LCInitData.Genesis.InitialHeight)
|
||||
|
||||
// verify that 2/3+ of the validator set signed the untrustedBlock
|
||||
if err := VerifyCommitFull(untrustedBlock.Commit, LCInitData.Genesis.Validators); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we can now trust the block
|
||||
initialBlock = untrustedBlock
|
||||
default:
|
||||
return nil, Error("No initial data was provided")
|
||||
|
||||
// This is done in the golang version but is optional and not strictly part of the protocol
|
||||
if err := CrossCheck(initialBlock, PeerList.Witnesses()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// initialize light store
|
||||
lightStore := new LightStore;
|
||||
lightStore.Add(newBlock);
|
||||
return (lightStore, OK);
|
||||
}
|
||||
|
||||
func CrossCheck(lb LightBlock, witnesses []Provider) error {
|
||||
for _, witness := range witnesses {
|
||||
witnessBlock := FetchLightBlock(witness, lb.Height)
|
||||
|
||||
if witnessBlock.Hash() != lb.Hash() {
|
||||
return Error("Witness has different block")
|
||||
}
|
||||
}
|
||||
return OK
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Implementation remark
|
||||
- none
|
||||
- Expected precondition
|
||||
- *LCInitData* contains either a genesis file of a lightblock
|
||||
- if genesis it passes `ValidateAndComplete()` see [Tendermint](https://informal.systems)
|
||||
- Expected postcondition
|
||||
- *lightStore* initialized with trusted lightblock. It has either been
|
||||
cross-checked (from genesis) or it has initial trust from the
|
||||
user.
|
||||
- Error condition
|
||||
- if precondition is violated
|
||||
- empty peerList
|
||||
|
||||
----
|
||||
|
||||
@@ -1175,4 +1175,4 @@ func Main (primary PeerID, lightStore LightStore, targetHeight Height)
|
||||
|
||||
[FN-ManifestFaulty-link]: https://github.com/tendermint/spec/blob/master/spec/blockchain/fullnode.md#fn-manifestfaulty
|
||||
|
||||
[arXiv]: https://arxiv.org/abs/1807.04938
|
||||
[arXiv]: https://arxiv.org/abs/1807.04938
|
||||
76
spec/light-client/verification/verification_003_draft.md
Normal file
76
spec/light-client/verification/verification_003_draft.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Light Client Verificaiton
|
||||
|
||||
#### **[LCV-FUNC-VERIFYCOMMITLIGHT.1]**
|
||||
|
||||
VerifyCommitLight verifies that 2/3+ of the signatures for a validator set were for
|
||||
a given blockID. The function will finish early and thus may not check all signatures.
|
||||
|
||||
```go
|
||||
func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID,
|
||||
height int64, commit *Commit) error {
|
||||
// run a basic validation of the arguments
|
||||
if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// calculate voting power needed
|
||||
votingPowerNeeded := vals.TotalVotingPower() * 2 / 3
|
||||
|
||||
var (
|
||||
val *Validator
|
||||
valIdx int32
|
||||
seenVals = make(map[int32]int, len(commit.Signatures))
|
||||
talliedVotingPower int64 = 0
|
||||
voteSignBytes []byte
|
||||
)
|
||||
for idx, commitSig := range commit.Signatures {
|
||||
// ignore all commit signatures that are not for the block
|
||||
if !commitSig.ForBlock() {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the vals and commit have a 1-to-1 correspondance we can retrieve
|
||||
// them by index else we need to retrieve them by address
|
||||
if lookUpByIndex {
|
||||
val = vals.Validators[idx]
|
||||
} else {
|
||||
valIdx, val = vals.GetByAddress(commitSig.ValidatorAddress)
|
||||
|
||||
// if the signature doesn't belong to anyone in the validator set
|
||||
// then we just skip over it
|
||||
if val == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// because we are getting validators by address we need to make sure
|
||||
// that the same validator doesn't commit twice
|
||||
if firstIndex, ok := seenVals[valIdx]; ok {
|
||||
secondIndex := idx
|
||||
return fmt.Errorf("double vote from %v (%d and %d)", val, firstIndex, secondIndex)
|
||||
}
|
||||
seenVals[valIdx] = idx
|
||||
}
|
||||
|
||||
voteSignBytes = commit.VoteSignBytes(chainID, int32(idx))
|
||||
|
||||
if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) {
|
||||
return fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature)
|
||||
}
|
||||
|
||||
// Add the voting power of the validator
|
||||
// to the tally
|
||||
talliedVotingPower += val.VotingPower
|
||||
|
||||
// check if we have enough signatures and can thus exit early
|
||||
if talliedVotingPower > votingPowerNeeded {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if got, needed := talliedVotingPower, votingPowerNeeded; got <= needed {
|
||||
return ErrNotEnoughVotingPowerSigned{Got: got, Needed: needed}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user