mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-20 23:14:37 +00:00
light: implement light block (#5298)
This commit is contained in:
-110
@@ -987,116 +987,6 @@ func CommitFromProto(cp *tmproto.Commit) (*Commit, error) {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// SignedHeader is a header along with the commits that prove it.
|
||||
// It is the basis of the light client.
|
||||
type SignedHeader struct {
|
||||
*Header `json:"header"`
|
||||
|
||||
Commit *Commit `json:"commit"`
|
||||
}
|
||||
|
||||
// ValidateBasic does basic consistency checks and makes sure the header
|
||||
// and commit are consistent.
|
||||
//
|
||||
// NOTE: This does not actually check the cryptographic signatures. Make sure
|
||||
// to use a Verifier to validate the signatures actually provide a
|
||||
// significantly strong proof for this header's validity.
|
||||
func (sh SignedHeader) ValidateBasic(chainID string) error {
|
||||
if sh.Header == nil {
|
||||
return errors.New("missing header")
|
||||
}
|
||||
if sh.Commit == nil {
|
||||
return errors.New("missing commit")
|
||||
}
|
||||
|
||||
if err := sh.Header.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("invalid header: %w", err)
|
||||
}
|
||||
if err := sh.Commit.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("invalid commit: %w", err)
|
||||
}
|
||||
|
||||
if sh.ChainID != chainID {
|
||||
return fmt.Errorf("header belongs to another chain %q, not %q", sh.ChainID, chainID)
|
||||
}
|
||||
|
||||
// Make sure the header is consistent with the commit.
|
||||
if sh.Commit.Height != sh.Height {
|
||||
return fmt.Errorf("header and commit height mismatch: %d vs %d", sh.Height, sh.Commit.Height)
|
||||
}
|
||||
if hhash, chash := sh.Hash(), sh.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) {
|
||||
return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of SignedHeader.
|
||||
func (sh SignedHeader) String() string {
|
||||
return sh.StringIndented("")
|
||||
}
|
||||
|
||||
// StringIndented returns an indented string representation of SignedHeader.
|
||||
//
|
||||
// Header
|
||||
// Commit
|
||||
func (sh SignedHeader) StringIndented(indent string) string {
|
||||
return fmt.Sprintf(`SignedHeader{
|
||||
%s %v
|
||||
%s %v
|
||||
%s}`,
|
||||
indent, sh.Header.StringIndented(indent+" "),
|
||||
indent, sh.Commit.StringIndented(indent+" "),
|
||||
indent)
|
||||
}
|
||||
|
||||
// ToProto converts SignedHeader to protobuf
|
||||
func (sh *SignedHeader) ToProto() *tmproto.SignedHeader {
|
||||
if sh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
psh := new(tmproto.SignedHeader)
|
||||
if sh.Header != nil {
|
||||
psh.Header = sh.Header.ToProto()
|
||||
}
|
||||
if sh.Commit != nil {
|
||||
psh.Commit = sh.Commit.ToProto()
|
||||
}
|
||||
|
||||
return psh
|
||||
}
|
||||
|
||||
// FromProto sets a protobuf SignedHeader to the given pointer.
|
||||
// It returns an error if the hader or the commit is invalid.
|
||||
func SignedHeaderFromProto(shp *tmproto.SignedHeader) (*SignedHeader, error) {
|
||||
if shp == nil {
|
||||
return nil, errors.New("nil SignedHeader")
|
||||
}
|
||||
|
||||
sh := new(SignedHeader)
|
||||
|
||||
if shp.Header != nil {
|
||||
h, err := HeaderFromProto(shp.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh.Header = &h
|
||||
}
|
||||
|
||||
if shp.Commit != nil {
|
||||
c, err := CommitFromProto(shp.Commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh.Commit = c
|
||||
}
|
||||
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Data contains the set of transactions included in the block
|
||||
type Data struct {
|
||||
|
||||
|
||||
@@ -543,59 +543,6 @@ func TestCommitToVoteSetWithVotesForNilBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignedHeaderValidateBasic(t *testing.T) {
|
||||
commit := randCommit(time.Now())
|
||||
chainID := "𠜎"
|
||||
timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC)
|
||||
h := Header{
|
||||
Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64},
|
||||
ChainID: chainID,
|
||||
Height: commit.Height,
|
||||
Time: timestamp,
|
||||
LastBlockID: commit.BlockID,
|
||||
LastCommitHash: commit.Hash(),
|
||||
DataHash: commit.Hash(),
|
||||
ValidatorsHash: commit.Hash(),
|
||||
NextValidatorsHash: commit.Hash(),
|
||||
ConsensusHash: commit.Hash(),
|
||||
AppHash: commit.Hash(),
|
||||
LastResultsHash: commit.Hash(),
|
||||
EvidenceHash: commit.Hash(),
|
||||
ProposerAddress: crypto.AddressHash([]byte("proposer_address")),
|
||||
}
|
||||
|
||||
validSignedHeader := SignedHeader{Header: &h, Commit: commit}
|
||||
validSignedHeader.Commit.BlockID.Hash = validSignedHeader.Hash()
|
||||
invalidSignedHeader := SignedHeader{}
|
||||
|
||||
testCases := []struct {
|
||||
testName string
|
||||
shHeader *Header
|
||||
shCommit *Commit
|
||||
expectErr bool
|
||||
}{
|
||||
{"Valid Signed Header", validSignedHeader.Header, validSignedHeader.Commit, false},
|
||||
{"Invalid Signed Header", invalidSignedHeader.Header, validSignedHeader.Commit, true},
|
||||
{"Invalid Signed Header", validSignedHeader.Header, invalidSignedHeader.Commit, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
sh := SignedHeader{
|
||||
Header: tc.shHeader,
|
||||
Commit: tc.shCommit,
|
||||
}
|
||||
assert.Equal(
|
||||
t,
|
||||
tc.expectErr,
|
||||
sh.ValidateBasic(validSignedHeader.Header.ChainID) != nil,
|
||||
"Validate Basic had an unexpected result",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockIDValidateBasic(t *testing.T) {
|
||||
validBlockID := BlockID{
|
||||
Hash: bytes.HexBytes{},
|
||||
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
)
|
||||
|
||||
// LightBlock is a SignedHeader and a ValidatorSet.
|
||||
// It is the basis of the light client
|
||||
type LightBlock struct {
|
||||
*SignedHeader `json:"signed_header"`
|
||||
ValidatorSet *ValidatorSet `json:"validator_set"`
|
||||
}
|
||||
|
||||
// ValidateBasic checks that the data is correct and consistent
|
||||
//
|
||||
// This does no verification of the signatures
|
||||
func (lb LightBlock) ValidateBasic(chainID string) error {
|
||||
if lb.SignedHeader == nil {
|
||||
return errors.New("missing signed header")
|
||||
}
|
||||
if lb.ValidatorSet == nil {
|
||||
return errors.New("missing validator set")
|
||||
}
|
||||
|
||||
if err := lb.SignedHeader.ValidateBasic(chainID); err != nil {
|
||||
return fmt.Errorf("invalid signed header: %w", err)
|
||||
}
|
||||
if err := lb.ValidatorSet.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("invalid validator set: %w", err)
|
||||
}
|
||||
|
||||
// make sure the validator set is consistent with the header
|
||||
if valSetHash := lb.ValidatorSet.Hash(); !bytes.Equal(lb.SignedHeader.ValidatorsHash, valSetHash) {
|
||||
return fmt.Errorf("expected validator hash of header to match validator set hash (%X != %X)",
|
||||
lb.SignedHeader.ValidatorsHash, valSetHash,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the LightBlock
|
||||
func (lb LightBlock) String() string {
|
||||
return lb.StringIndented("")
|
||||
}
|
||||
|
||||
// StringIndented returns an indented string representation of the LightBlock
|
||||
//
|
||||
// SignedHeader
|
||||
// ValidatorSet
|
||||
func (lb LightBlock) StringIndented(indent string) string {
|
||||
return fmt.Sprintf(`LightBlock{
|
||||
%s %v
|
||||
%s %v
|
||||
%s}`,
|
||||
indent, lb.SignedHeader.StringIndented(indent+" "),
|
||||
indent, lb.ValidatorSet.StringIndented(indent+" "),
|
||||
indent)
|
||||
}
|
||||
|
||||
// ToProto converts the LightBlock to protobuf
|
||||
func (lb *LightBlock) ToProto() (*tmproto.LightBlock, error) {
|
||||
if lb == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
lbp := new(tmproto.LightBlock)
|
||||
var err error
|
||||
if lb.SignedHeader != nil {
|
||||
lbp.SignedHeader = lb.SignedHeader.ToProto()
|
||||
}
|
||||
if lb.ValidatorSet != nil {
|
||||
lbp.ValidatorSet, err = lb.ValidatorSet.ToProto()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return lbp, nil
|
||||
}
|
||||
|
||||
// LightBlockFromProto converts from protobuf back into the Lightblock.
|
||||
// An error is returned if either the validator set or signed header are invalid
|
||||
func LightBlockFromProto(pb *tmproto.LightBlock) (*LightBlock, error) {
|
||||
if pb == nil {
|
||||
return nil, errors.New("nil light block")
|
||||
}
|
||||
|
||||
lb := new(LightBlock)
|
||||
|
||||
if pb.SignedHeader != nil {
|
||||
sh, err := SignedHeaderFromProto(pb.SignedHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lb.SignedHeader = sh
|
||||
}
|
||||
|
||||
if pb.ValidatorSet != nil {
|
||||
vals, err := ValidatorSetFromProto(pb.ValidatorSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lb.ValidatorSet = vals
|
||||
}
|
||||
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// SignedHeader is a header along with the commits that prove it.
|
||||
type SignedHeader struct {
|
||||
*Header `json:"header"`
|
||||
|
||||
Commit *Commit `json:"commit"`
|
||||
}
|
||||
|
||||
// ValidateBasic does basic consistency checks and makes sure the header
|
||||
// and commit are consistent.
|
||||
//
|
||||
// NOTE: This does not actually check the cryptographic signatures. Make sure
|
||||
// to use a Verifier to validate the signatures actually provide a
|
||||
// significantly strong proof for this header's validity.
|
||||
func (sh SignedHeader) ValidateBasic(chainID string) error {
|
||||
if sh.Header == nil {
|
||||
return errors.New("missing header")
|
||||
}
|
||||
if sh.Commit == nil {
|
||||
return errors.New("missing commit")
|
||||
}
|
||||
|
||||
if err := sh.Header.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("invalid header: %w", err)
|
||||
}
|
||||
if err := sh.Commit.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("invalid commit: %w", err)
|
||||
}
|
||||
|
||||
if sh.ChainID != chainID {
|
||||
return fmt.Errorf("header belongs to another chain %q, not %q", sh.ChainID, chainID)
|
||||
}
|
||||
|
||||
// Make sure the header is consistent with the commit.
|
||||
if sh.Commit.Height != sh.Height {
|
||||
return fmt.Errorf("header and commit height mismatch: %d vs %d", sh.Height, sh.Commit.Height)
|
||||
}
|
||||
if hhash, chash := sh.Hash(), sh.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) {
|
||||
return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a string representation of SignedHeader.
|
||||
func (sh SignedHeader) String() string {
|
||||
return sh.StringIndented("")
|
||||
}
|
||||
|
||||
// StringIndented returns an indented string representation of SignedHeader.
|
||||
//
|
||||
// Header
|
||||
// Commit
|
||||
func (sh SignedHeader) StringIndented(indent string) string {
|
||||
return fmt.Sprintf(`SignedHeader{
|
||||
%s %v
|
||||
%s %v
|
||||
%s}`,
|
||||
indent, sh.Header.StringIndented(indent+" "),
|
||||
indent, sh.Commit.StringIndented(indent+" "),
|
||||
indent)
|
||||
}
|
||||
|
||||
// ToProto converts SignedHeader to protobuf
|
||||
func (sh *SignedHeader) ToProto() *tmproto.SignedHeader {
|
||||
if sh == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
psh := new(tmproto.SignedHeader)
|
||||
if sh.Header != nil {
|
||||
psh.Header = sh.Header.ToProto()
|
||||
}
|
||||
if sh.Commit != nil {
|
||||
psh.Commit = sh.Commit.ToProto()
|
||||
}
|
||||
|
||||
return psh
|
||||
}
|
||||
|
||||
// FromProto sets a protobuf SignedHeader to the given pointer.
|
||||
// It returns an error if the header or the commit is invalid.
|
||||
func SignedHeaderFromProto(shp *tmproto.SignedHeader) (*SignedHeader, error) {
|
||||
if shp == nil {
|
||||
return nil, errors.New("nil SignedHeader")
|
||||
}
|
||||
|
||||
sh := new(SignedHeader)
|
||||
|
||||
if shp.Header != nil {
|
||||
h, err := HeaderFromProto(shp.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh.Header = &h
|
||||
}
|
||||
|
||||
if shp.Commit != nil {
|
||||
c, err := CommitFromProto(shp.Commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sh.Commit = c
|
||||
}
|
||||
|
||||
return sh, nil
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/proto/tendermint/version"
|
||||
)
|
||||
|
||||
func TestLightBlockValidateBasic(t *testing.T) {
|
||||
header := makeRandHeader()
|
||||
commit := randCommit(time.Now())
|
||||
vals, _ := RandValidatorSet(5, 1)
|
||||
header.Height = commit.Height
|
||||
header.LastBlockID = commit.BlockID
|
||||
header.ValidatorsHash = vals.Hash()
|
||||
vals2, _ := RandValidatorSet(3, 1)
|
||||
vals3 := vals.Copy()
|
||||
vals3.Proposer = &Validator{}
|
||||
commit.BlockID.Hash = header.Hash()
|
||||
|
||||
sh := &SignedHeader{
|
||||
Header: &header,
|
||||
Commit: commit,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sh *SignedHeader
|
||||
vals *ValidatorSet
|
||||
expectErr bool
|
||||
}{
|
||||
{"valid light block", sh, vals, false},
|
||||
{"hashes don't match", sh, vals2, true},
|
||||
{"invalid validator set", sh, vals3, true},
|
||||
{"invalid signed header", &SignedHeader{Header: &header, Commit: randCommit(time.Now())}, vals, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
lightBlock := LightBlock{
|
||||
SignedHeader: tc.sh,
|
||||
ValidatorSet: tc.vals,
|
||||
}
|
||||
err := lightBlock.ValidateBasic(header.ChainID)
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err, tc.name)
|
||||
} else {
|
||||
assert.NoError(t, err, tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLightBlockProtobuf(t *testing.T) {
|
||||
header := makeRandHeader()
|
||||
commit := randCommit(time.Now())
|
||||
vals, _ := RandValidatorSet(5, 1)
|
||||
header.Height = commit.Height
|
||||
header.LastBlockID = commit.BlockID
|
||||
header.ValidatorsHash = vals.Hash()
|
||||
vals3 := vals.Copy()
|
||||
vals3.Proposer = &Validator{}
|
||||
commit.BlockID.Hash = header.Hash()
|
||||
|
||||
sh := &SignedHeader{
|
||||
Header: &header,
|
||||
Commit: commit,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
sh *SignedHeader
|
||||
vals *ValidatorSet
|
||||
toProtoErr bool
|
||||
toBlockErr bool
|
||||
}{
|
||||
{"valid light block", sh, vals, false, false},
|
||||
{"empty signed header", &SignedHeader{}, vals, false, false},
|
||||
{"empty validator set", sh, &ValidatorSet{}, false, true},
|
||||
{"empty light block", &SignedHeader{}, &ValidatorSet{}, false, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
lightBlock := &LightBlock{
|
||||
SignedHeader: tc.sh,
|
||||
ValidatorSet: tc.vals,
|
||||
}
|
||||
lbp, err := lightBlock.ToProto()
|
||||
if tc.toProtoErr {
|
||||
assert.Error(t, err, tc.name)
|
||||
} else {
|
||||
assert.NoError(t, err, tc.name)
|
||||
}
|
||||
|
||||
lb, err := LightBlockFromProto(lbp)
|
||||
if tc.toBlockErr {
|
||||
assert.Error(t, err, tc.name)
|
||||
} else {
|
||||
assert.NoError(t, err, tc.name)
|
||||
assert.Equal(t, lightBlock, lb)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSignedHeaderValidateBasic(t *testing.T) {
|
||||
commit := randCommit(time.Now())
|
||||
chainID := "𠜎"
|
||||
timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC)
|
||||
h := Header{
|
||||
Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64},
|
||||
ChainID: chainID,
|
||||
Height: commit.Height,
|
||||
Time: timestamp,
|
||||
LastBlockID: commit.BlockID,
|
||||
LastCommitHash: commit.Hash(),
|
||||
DataHash: commit.Hash(),
|
||||
ValidatorsHash: commit.Hash(),
|
||||
NextValidatorsHash: commit.Hash(),
|
||||
ConsensusHash: commit.Hash(),
|
||||
AppHash: commit.Hash(),
|
||||
LastResultsHash: commit.Hash(),
|
||||
EvidenceHash: commit.Hash(),
|
||||
ProposerAddress: crypto.AddressHash([]byte("proposer_address")),
|
||||
}
|
||||
|
||||
validSignedHeader := SignedHeader{Header: &h, Commit: commit}
|
||||
validSignedHeader.Commit.BlockID.Hash = validSignedHeader.Hash()
|
||||
invalidSignedHeader := SignedHeader{}
|
||||
|
||||
testCases := []struct {
|
||||
testName string
|
||||
shHeader *Header
|
||||
shCommit *Commit
|
||||
expectErr bool
|
||||
}{
|
||||
{"Valid Signed Header", validSignedHeader.Header, validSignedHeader.Commit, false},
|
||||
{"Invalid Signed Header", invalidSignedHeader.Header, validSignedHeader.Commit, true},
|
||||
{"Invalid Signed Header", validSignedHeader.Header, invalidSignedHeader.Commit, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
sh := SignedHeader{
|
||||
Header: tc.shHeader,
|
||||
Commit: tc.shCommit,
|
||||
}
|
||||
assert.Equal(
|
||||
t,
|
||||
tc.expectErr,
|
||||
sh.ValidateBasic(validSignedHeader.Header.ChainID) != nil,
|
||||
"Validate Basic had an unexpected result",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user