Merge pull request #1815 from tendermint/jae/literefactor4
ValidatorSet change delayed by 1 block, and lite refactor (#2)
This commit is contained in:
commit
06a157ad06
|
@ -28,6 +28,9 @@ scripts/cutWALUntil/cutWALUntil
|
|||
libs/pubsub/query/fuzz_test/output
|
||||
shunit2
|
||||
|
||||
.tendermint-lite
|
||||
addrbook.json
|
||||
|
||||
*/vendor
|
||||
*/.glide
|
||||
.terraform
|
||||
|
|
|
@ -588,7 +588,7 @@ BREAKING CHANGES:
|
|||
- use scripts/wal2json to convert to json for debugging
|
||||
|
||||
FEATURES:
|
||||
- new `certifiers` pkg contains the tendermint light-client library (name subject to change)!
|
||||
- new `Verifiers` pkg contains the tendermint light-client library (name subject to change)!
|
||||
- rpc: `/genesis` includes the `app_options` .
|
||||
- rpc: `/abci_query` takes an additional `height` parameter to support historical queries.
|
||||
- rpc/client: new ABCIQueryWithOptions supports options like `trusted` (set false to get a proof) and `height` to query a historical height.
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
BREAKING CHANGES:
|
||||
- [types] CanonicalTime uses nanoseconds instead of clipping to ms
|
||||
- breaks serialization/signing of all messages with a timestamp
|
||||
- [types] Header ...
|
||||
- [state] Add NextValidatorSet, changes on-disk representation of state
|
||||
- [state] Validator set changes are delayed by one block (!)
|
||||
- [lite] Complete refactor of the package
|
||||
- [rpc] `/commit` returns a `signed_header` field instead of everything being
|
||||
top-level
|
||||
- [abci] Removed Fee from ResponseDeliverTx and ResponseCheckTx
|
||||
- [tools] Removed `make ensure_deps` in favor of `make get_vendor_deps`
|
||||
- [p2p] Remove salsa and ripemd primitives, in favor of using chacha as a stream cipher, and hkdf
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8"
|
||||
digest = "1:6aabc1566d6351115d561d038da82a4c19b46c3b6e17f4a0a2fa60260663dc79"
|
||||
name = "github.com/btcsuite/btcd"
|
||||
packages = ["btcec"]
|
||||
pruneopts = "UT"
|
||||
revision = "f5e261fc9ec3437697fb31d8b38453c293204b29"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1d8e1cb71c33a9470bbbae09bfec09db43c6bf358dfcae13cd8807c4e2a9a2bf"
|
||||
digest = "1:df684ed7fed3fb406ec421424aaf5fc9c63ccc2f428b25b842da78e634482e4b"
|
||||
name = "github.com/btcsuite/btcutil"
|
||||
packages = [
|
||||
"base58",
|
||||
|
@ -59,7 +59,7 @@
|
|||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:fdf5169073fb0ad6dc12a70c249145e30f4058647bea25f0abd48b6d9f228a11"
|
||||
digest = "1:fa30c0652956e159cdb97dcb2ef8b8db63ed668c02a5c3a40961c8f0641252fe"
|
||||
name = "github.com/go-kit/kit"
|
||||
packages = [
|
||||
"log",
|
||||
|
@ -91,7 +91,7 @@
|
|||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e"
|
||||
digest = "1:212285efb97b9ec2e20550d81f0446cb7897e57cbdfd7301b1363ab113d8be45"
|
||||
name = "github.com/gogo/protobuf"
|
||||
packages = [
|
||||
"gogoproto",
|
||||
|
@ -106,7 +106,7 @@
|
|||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260"
|
||||
digest = "1:cb22af0ed7c72d495d8be1106233ee553898950f15fd3f5404406d44c2e86888"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = [
|
||||
"proto",
|
||||
|
@ -137,7 +137,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240"
|
||||
digest = "1:8951fe6e358876736d8fa1f3992624fdbb2dec6bc49401c1381d1ef8abbb544f"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -225,7 +225,7 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0"
|
||||
digest = "1:98225904b7abff96c052b669b25788f18225a36673fba022fb93514bb9a2a64e"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = [
|
||||
"prometheus",
|
||||
|
@ -236,7 +236,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4"
|
||||
digest = "1:0f37e09b3e92aaeda5991581311f8dbf38944b36a3edec61cc2d1991f527554a"
|
||||
name = "github.com/prometheus/client_model"
|
||||
packages = ["go"]
|
||||
pruneopts = "UT"
|
||||
|
@ -256,7 +256,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290"
|
||||
digest = "1:a37c98f4b7a66bb5c539c0539f0915a74ef1c8e0b3b6f45735289d94cae92bfd"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -275,7 +275,7 @@
|
|||
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84"
|
||||
digest = "1:37ace7f35375adec11634126944bdc45a673415e2fcc07382d03b75ec76ea94c"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -294,7 +294,7 @@
|
|||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e"
|
||||
digest = "1:627ab2f549a6a55c44f46fa24a4307f4d0da81bfc7934ed0473bf38b24051d26"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
|
@ -326,7 +326,7 @@
|
|||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6"
|
||||
digest = "1:73697231b93fb74a73ebd8384b68b9a60c57ea6b13c56d2425414566a72c8e6d"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = [
|
||||
"assert",
|
||||
|
@ -338,7 +338,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b"
|
||||
digest = "1:922191411ad8f61bcd8018ac127589bb489712c1d1a0ab2497aca4b16de417d2"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
|
@ -359,7 +359,7 @@
|
|||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722"
|
||||
digest = "1:203b409c21115233a576f99e8f13d8e07ad82b25500491f7e1cca12588fb3232"
|
||||
name = "github.com/tendermint/ed25519"
|
||||
packages = [
|
||||
".",
|
||||
|
@ -370,16 +370,16 @@
|
|||
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e9113641c839c21d8eaeb2c907c7276af1eddeed988df8322168c56b7e06e0e1"
|
||||
digest = "1:e0a2a4be1e20c305badc2b0a7a9ab7fef6da500763bec23ab81df3b5f9eec9ee"
|
||||
name = "github.com/tendermint/go-amino"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2106ca61d91029c931fd54968c2bb02dc96b1412"
|
||||
version = "0.10.1"
|
||||
revision = "a8328986c1608950fa5d3d1c0472cccc4f8fc02c"
|
||||
version = "v0.12.0-rc0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:c31a37cafc12315b8bd745c8ad6a006ac25350472488162a821e557b3e739d67"
|
||||
digest = "1:df132ec33d5acb4a1ab58d637f1bc3557be49456ca59b9198f5c1e7fa32e0d31"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"bcrypt",
|
||||
|
@ -398,10 +398,10 @@
|
|||
"salsa20/salsa",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c126467f60eb25f8f27e5a981f32a87e3965053f"
|
||||
revision = "56440b844dfe139a8ac053f4ecac0b20b79058f4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1"
|
||||
digest = "1:04dda8391c3e2397daf254ac68003f30141c069b228d06baec8324a5f81dc1e9"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
|
@ -428,7 +428,7 @@
|
|||
revision = "3dc4335d56c789b04b0ba99b7a37249d9b614314"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
|
||||
digest = "1:7509ba4347d1f8de6ae9be8818b0cd1abc3deeffe28aeaf4be6d4b6b5178d9ca"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
|
@ -459,7 +459,7 @@
|
|||
revision = "daca94659cb50e9f37c1b834680f2e46358f10b0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74"
|
||||
digest = "1:4515e3030c440845b046354fd5d57671238428b820deebce2e9dabb5cd3c51ac"
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [
|
||||
".",
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
|
||||
[[constraint]]
|
||||
name = "github.com/tendermint/go-amino"
|
||||
version = "=v0.10.1"
|
||||
version = "v0.12.0-rc0"
|
||||
|
||||
[[constraint]]
|
||||
name = "google.golang.org/grpc"
|
||||
|
|
4
Makefile
4
Makefile
|
@ -200,11 +200,11 @@ vagrant_test:
|
|||
### go tests
|
||||
test:
|
||||
@echo "--> Running go test"
|
||||
@GOCACHE=off go test $(PACKAGES)
|
||||
@GOCACHE=off go test -p 1 $(PACKAGES)
|
||||
|
||||
test_race:
|
||||
@echo "--> Running go test --race"
|
||||
@go test -v -race $(PACKAGES)
|
||||
@GOCACHE=off go test -p 1 -v -race $(PACKAGES)
|
||||
|
||||
|
||||
########################################
|
||||
|
|
|
@ -49,7 +49,7 @@ func TestNewBlockStore(t *testing.T) {
|
|||
return nil, nil
|
||||
})
|
||||
require.NotNil(t, panicErr, "#%d panicCauser: %q expected a panic", i, tt.data)
|
||||
assert.Contains(t, panicErr.Error(), tt.wantErr, "#%d data: %q", i, tt.data)
|
||||
assert.Contains(t, fmt.Sprintf("%#v", panicErr), tt.wantErr, "#%d data: %q", i, tt.data)
|
||||
}
|
||||
|
||||
db.Set(blockStoreKey, nil)
|
||||
|
@ -238,7 +238,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
|||
if subStr := tuple.wantPanic; subStr != "" {
|
||||
if panicErr == nil {
|
||||
t.Errorf("#%d: want a non-nil panic", i)
|
||||
} else if got := panicErr.Error(); !strings.Contains(got, subStr) {
|
||||
} else if got := fmt.Sprintf("%#v", panicErr); !strings.Contains(got, subStr) {
|
||||
t.Errorf("#%d:\n\tgotErr: %q\nwant substring: %q", i, got, subStr)
|
||||
}
|
||||
continue
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/lite/proxy"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
@ -66,17 +65,21 @@ func runProxy(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
// First, connect a client
|
||||
logger.Info("Connecting to source HTTP client...")
|
||||
node := rpcclient.NewHTTP(nodeAddr, "/websocket")
|
||||
|
||||
cert, err := proxy.GetCertifier(chainID, home, nodeAddr)
|
||||
logger.Info("Constructing Verifier...")
|
||||
cert, err := proxy.NewVerifier(chainID, home, node, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
return cmn.ErrorWrap(err, "constructing Verifier")
|
||||
}
|
||||
cert.SetLogger(logger)
|
||||
sc := proxy.SecureClient(node, cert)
|
||||
|
||||
logger.Info("Starting proxy...")
|
||||
err = proxy.StartProxy(sc, listenAddr, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
return cmn.ErrorWrap(err, "starting proxy")
|
||||
}
|
||||
|
||||
cmn.TrapSignal(func() {
|
||||
|
|
|
@ -295,14 +295,14 @@ func TestReactorRecordsBlockParts(t *testing.T) {
|
|||
require.Equal(t, 1, ps.BlockPartsSent(), "number of block parts sent should stay the same")
|
||||
}
|
||||
|
||||
// Test we record votes from other peers
|
||||
// Test we record votes from other peers.
|
||||
func TestReactorRecordsVotes(t *testing.T) {
|
||||
// create dummy peer
|
||||
// Create dummy peer.
|
||||
peer := p2pdummy.NewPeer()
|
||||
ps := NewPeerState(peer).SetLogger(log.TestingLogger())
|
||||
peer.Set(types.PeerStateKey, ps)
|
||||
|
||||
// create reactor
|
||||
// Create reactor.
|
||||
css := randConsensusNet(1, "consensus_reactor_records_votes_test", newMockTickerFunc(true), newPersistentKVStore)
|
||||
reactor := NewConsensusReactor(css[0], false) // so we dont start the consensus states
|
||||
reactor.SetEventBus(css[0].eventBus)
|
||||
|
@ -540,7 +540,7 @@ func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{}
|
|||
err := validateBlock(newBlock, activeVals)
|
||||
assert.Nil(t, err)
|
||||
for _, tx := range txs {
|
||||
css[j].mempool.CheckTx(tx, nil)
|
||||
err := css[j].mempool.CheckTx(tx, nil)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
}, css)
|
||||
|
|
|
@ -264,15 +264,15 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
|||
stateBlockHeight := state.LastBlockHeight
|
||||
h.logger.Info("ABCI Replay Blocks", "appHeight", appBlockHeight, "storeHeight", storeBlockHeight, "stateHeight", stateBlockHeight)
|
||||
|
||||
// If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain
|
||||
// If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain.
|
||||
if appBlockHeight == 0 {
|
||||
validators := types.TM2PB.Validators(state.Validators)
|
||||
nextVals := types.TM2PB.Validators(state.NextValidators) // state.Validators would work too.
|
||||
csParams := types.TM2PB.ConsensusParams(h.genDoc.ConsensusParams)
|
||||
req := abci.RequestInitChain{
|
||||
Time: h.genDoc.GenesisTime,
|
||||
ChainId: h.genDoc.ChainID,
|
||||
ConsensusParams: csParams,
|
||||
Validators: validators,
|
||||
Validators: nextVals,
|
||||
AppStateBytes: h.genDoc.AppState,
|
||||
}
|
||||
res, err := proxyApp.Consensus().InitChainSync(req)
|
||||
|
@ -280,9 +280,7 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// if the app returned validators
|
||||
// or consensus params, update the state
|
||||
// with the them
|
||||
// If the app returned validators or consensus params, update the state.
|
||||
if len(res.Validators) > 0 {
|
||||
vals, err := types.PB2TM.Validators(res.Validators)
|
||||
if err != nil {
|
||||
|
@ -296,7 +294,7 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight
|
|||
sm.SaveState(h.stateDB, state)
|
||||
}
|
||||
|
||||
// First handle edge cases and constraints on the storeBlockHeight
|
||||
// First handle edge cases and constraints on the storeBlockHeight.
|
||||
if storeBlockHeight == 0 {
|
||||
return appHash, checkAppHash(state, appHash)
|
||||
|
||||
|
|
|
@ -74,7 +74,6 @@ type ConsensusState struct {
|
|||
privValidator types.PrivValidator // for signing votes
|
||||
|
||||
// services for creating and executing blocks
|
||||
// TODO: encapsulate all of this in one "BlockManager"
|
||||
blockExec *sm.BlockExecutor
|
||||
blockStore sm.BlockStore
|
||||
mempool sm.Mempool
|
||||
|
@ -197,6 +196,15 @@ func (cs *ConsensusState) GetState() sm.State {
|
|||
return cs.state.Copy()
|
||||
}
|
||||
|
||||
// GetLastHeight returns the last height committed.
|
||||
// If there were no blocks, returns 0.
|
||||
func (cs *ConsensusState) GetLastHeight() int64 {
|
||||
cs.mtx.Lock()
|
||||
defer cs.mtx.Unlock()
|
||||
|
||||
return cs.RoundState.Height - 1
|
||||
}
|
||||
|
||||
// GetRoundState returns a shallow copy of the internal consensus state.
|
||||
func (cs *ConsensusState) GetRoundState() *cstypes.RoundState {
|
||||
cs.mtx.RLock()
|
||||
|
|
|
@ -27,6 +27,7 @@ func initializeValidatorState(valAddr []byte, height int64) dbm.DB {
|
|||
LastBlockHeight: 0,
|
||||
LastBlockTime: time.Now(),
|
||||
Validators: valSet,
|
||||
NextValidators: valSet.CopyIncrementAccum(1),
|
||||
LastHeightValidatorsChanged: 1,
|
||||
ConsensusParams: types.ConsensusParams{
|
||||
EvidenceParams: types.EvidenceParams{
|
||||
|
|
|
@ -291,7 +291,8 @@ loop:
|
|||
}
|
||||
|
||||
func (state *state) add(clientID string, q Query, ch chan<- interface{}) {
|
||||
// add query if needed
|
||||
|
||||
// initialize clientToChannelMap per query if needed
|
||||
if _, ok := state.queries[q]; !ok {
|
||||
state.queries[q] = make(map[string]chan<- interface{})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var _ Verifier = (*BaseVerifier)(nil)
|
||||
|
||||
// BaseVerifier lets us check the validity of SignedHeaders at height or
|
||||
// later, requiring sufficient votes (> 2/3) from the given valset.
|
||||
// To certify blocks produced by a blockchain with mutable validator sets,
|
||||
// use the DynamicVerifier.
|
||||
// TODO: Handle unbonding time.
|
||||
type BaseVerifier struct {
|
||||
chainID string
|
||||
height int64
|
||||
valset *types.ValidatorSet
|
||||
}
|
||||
|
||||
// NewBaseVerifier returns a new Verifier initialized with a validator set at
|
||||
// some height.
|
||||
func NewBaseVerifier(chainID string, height int64, valset *types.ValidatorSet) *BaseVerifier {
|
||||
if valset.IsNilOrEmpty() {
|
||||
panic("NewBaseVerifier requires a valid valset")
|
||||
}
|
||||
return &BaseVerifier{
|
||||
chainID: chainID,
|
||||
height: height,
|
||||
valset: valset,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements Verifier.
|
||||
func (bc *BaseVerifier) ChainID() string {
|
||||
return bc.chainID
|
||||
}
|
||||
|
||||
// Implements Verifier.
|
||||
func (bc *BaseVerifier) Certify(signedHeader types.SignedHeader) error {
|
||||
|
||||
// We can't certify commits older than bc.height.
|
||||
if signedHeader.Height < bc.height {
|
||||
return cmn.NewError("BaseVerifier height is %v, cannot certify height %v",
|
||||
bc.height, signedHeader.Height)
|
||||
}
|
||||
|
||||
// We can't certify with the wrong validator set.
|
||||
if !bytes.Equal(signedHeader.ValidatorsHash,
|
||||
bc.valset.Hash()) {
|
||||
return lerr.ErrUnexpectedValidators(signedHeader.ValidatorsHash, bc.valset.Hash())
|
||||
}
|
||||
|
||||
// Do basic sanity checks.
|
||||
err := signedHeader.ValidateBasic(bc.chainID)
|
||||
if err != nil {
|
||||
return cmn.ErrorWrap(err, "in certify")
|
||||
}
|
||||
|
||||
// Check commit signatures.
|
||||
err = bc.valset.VerifyCommit(
|
||||
bc.chainID, signedHeader.Commit.BlockID,
|
||||
signedHeader.Height, signedHeader.Commit)
|
||||
if err != nil {
|
||||
return cmn.ErrorWrap(err, "in certify")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func TestBaseCert(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
keys := genPrivKeys(4)
|
||||
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
||||
vals := keys.ToValidators(20, 10)
|
||||
// and a Verifier based on our known set
|
||||
chainID := "test-static"
|
||||
cert := NewBaseVerifier(chainID, 2, vals)
|
||||
|
||||
cases := []struct {
|
||||
keys privKeys
|
||||
vals *types.ValidatorSet
|
||||
height int64
|
||||
first, last int // who actually signs
|
||||
proper bool // true -> expect no error
|
||||
changed bool // true -> expect validator change error
|
||||
}{
|
||||
// height regression
|
||||
{keys, vals, 1, 0, len(keys), false, false},
|
||||
// perfect, signed by everyone
|
||||
{keys, vals, 2, 0, len(keys), true, false},
|
||||
// skip little guy is okay
|
||||
{keys, vals, 3, 1, len(keys), true, false},
|
||||
// but not the big guy
|
||||
{keys, vals, 4, 0, len(keys) - 1, false, false},
|
||||
// Changing the power a little bit breaks the static validator.
|
||||
// The sigs are enough, but the validator hash is unknown.
|
||||
{keys, keys.ToValidators(20, 11), 5, 0, len(keys), false, true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
sh := tc.keys.GenSignedHeader(chainID, tc.height, nil, tc.vals, tc.vals,
|
||||
[]byte("foo"), []byte("params"), []byte("results"), tc.first, tc.last)
|
||||
err := cert.Certify(sh)
|
||||
if tc.proper {
|
||||
assert.Nil(err, "%+v", err)
|
||||
} else {
|
||||
assert.NotNil(err)
|
||||
if tc.changed {
|
||||
assert.True(lerr.IsErrUnexpectedValidators(err), "%+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package client_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
)
|
||||
|
||||
var node *nm.Node
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// start a tendermint node (and merkleeyes) in the background to test against
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
node = rpctest.StartTendermint(app)
|
||||
code := m.Run()
|
||||
|
||||
// and shut down proper at the end
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
os.Exit(code)
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
/*
|
||||
Package client defines a provider that uses a rpcclient
|
||||
to get information, which is used to get new headers
|
||||
and validators directly from a node.
|
||||
and validators directly from a Tendermint client.
|
||||
*/
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
// SignStatusClient combines a SignClient and StatusClient.
|
||||
|
@ -23,119 +23,112 @@ type SignStatusClient interface {
|
|||
}
|
||||
|
||||
type provider struct {
|
||||
node SignStatusClient
|
||||
lastHeight int64
|
||||
logger log.Logger
|
||||
chainID string
|
||||
client SignStatusClient
|
||||
}
|
||||
|
||||
// NewProvider can wrap any rpcclient to expose it as
|
||||
// a read-only provider.
|
||||
func NewProvider(node SignStatusClient) lite.Provider {
|
||||
return &provider{node: node}
|
||||
// NewProvider implements Provider (but not PersistentProvider).
|
||||
func NewProvider(chainID string, client SignStatusClient) lite.Provider {
|
||||
return &provider{
|
||||
logger: log.NewNopLogger(),
|
||||
chainID: chainID,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHTTPProvider can connect to a tendermint json-rpc endpoint
|
||||
// at the given url, and uses that as a read-only provider.
|
||||
func NewHTTPProvider(remote string) lite.Provider {
|
||||
return &provider{
|
||||
node: rpcclient.NewHTTP(remote, "/websocket"),
|
||||
}
|
||||
func NewHTTPProvider(chainID, remote string) lite.Provider {
|
||||
return NewProvider(chainID, rpcclient.NewHTTP(remote, "/websocket"))
|
||||
}
|
||||
|
||||
// StatusClient returns the internal node as a StatusClient
|
||||
// Implements Provider.
|
||||
func (p *provider) SetLogger(logger log.Logger) {
|
||||
logger = logger.With("module", "lite/client")
|
||||
p.logger = logger
|
||||
}
|
||||
|
||||
// StatusClient returns the internal client as a StatusClient
|
||||
func (p *provider) StatusClient() rpcclient.StatusClient {
|
||||
return p.node
|
||||
return p.client
|
||||
}
|
||||
|
||||
// StoreCommit is a noop, as clients can only read from the chain...
|
||||
func (p *provider) StoreCommit(_ lite.FullCommit) error { return nil }
|
||||
|
||||
// GetHash gets the most recent validator and sees if it matches
|
||||
//
|
||||
// TODO: improve when the rpc interface supports more functionality
|
||||
func (p *provider) GetByHash(hash []byte) (lite.FullCommit, error) {
|
||||
var fc lite.FullCommit
|
||||
vals, err := p.node.Validators(nil)
|
||||
// if we get no validators, or a different height, return an error
|
||||
// LatestFullCommit implements Provider.
|
||||
func (p *provider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (fc lite.FullCommit, err error) {
|
||||
if chainID != p.chainID {
|
||||
err = fmt.Errorf("expected chainID %s, got %s", p.chainID, chainID)
|
||||
return
|
||||
}
|
||||
if maxHeight != 0 && maxHeight < minHeight {
|
||||
err = fmt.Errorf("need maxHeight == 0 or minHeight <= maxHeight, got min %v and max %v",
|
||||
minHeight, maxHeight)
|
||||
return
|
||||
}
|
||||
commit, err := p.fetchLatestCommit(minHeight, maxHeight)
|
||||
if err != nil {
|
||||
return fc, err
|
||||
return
|
||||
}
|
||||
p.updateHeight(vals.BlockHeight)
|
||||
vhash := types.NewValidatorSet(vals.Validators).Hash()
|
||||
if !bytes.Equal(hash, vhash) {
|
||||
return fc, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
return p.seedFromVals(vals)
|
||||
fc, err = p.fillFullCommit(commit.SignedHeader)
|
||||
return
|
||||
}
|
||||
|
||||
// GetByHeight gets the validator set by height
|
||||
func (p *provider) GetByHeight(h int64) (fc lite.FullCommit, err error) {
|
||||
commit, err := p.node.Commit(&h)
|
||||
if err != nil {
|
||||
return fc, err
|
||||
}
|
||||
return p.seedFromCommit(commit)
|
||||
}
|
||||
|
||||
// LatestCommit returns the newest commit stored.
|
||||
func (p *provider) LatestCommit() (fc lite.FullCommit, err error) {
|
||||
commit, err := p.GetLatestCommit()
|
||||
if err != nil {
|
||||
return fc, err
|
||||
}
|
||||
return p.seedFromCommit(commit)
|
||||
}
|
||||
|
||||
// GetLatestCommit should return the most recent commit there is,
|
||||
// which handles queries for future heights as per the semantics
|
||||
// of GetByHeight.
|
||||
func (p *provider) GetLatestCommit() (*ctypes.ResultCommit, error) {
|
||||
status, err := p.node.Status()
|
||||
// fetchLatestCommit fetches the latest commit from the client.
|
||||
func (p *provider) fetchLatestCommit(minHeight int64, maxHeight int64) (*ctypes.ResultCommit, error) {
|
||||
status, err := p.client.Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.node.Commit(&status.SyncInfo.LatestBlockHeight)
|
||||
if status.SyncInfo.LatestBlockHeight < minHeight {
|
||||
err = fmt.Errorf("provider is at %v but require minHeight=%v",
|
||||
status.SyncInfo.LatestBlockHeight, minHeight)
|
||||
return nil, err
|
||||
}
|
||||
if maxHeight == 0 {
|
||||
maxHeight = status.SyncInfo.LatestBlockHeight
|
||||
} else if status.SyncInfo.LatestBlockHeight < maxHeight {
|
||||
maxHeight = status.SyncInfo.LatestBlockHeight
|
||||
}
|
||||
return p.client.Commit(&maxHeight)
|
||||
}
|
||||
|
||||
// CommitFromResult ...
|
||||
func CommitFromResult(result *ctypes.ResultCommit) lite.Commit {
|
||||
return (lite.Commit)(result.SignedHeader)
|
||||
// Implements Provider.
|
||||
func (p *provider) ValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
|
||||
return p.getValidatorSet(chainID, height)
|
||||
}
|
||||
|
||||
func (p *provider) seedFromVals(vals *ctypes.ResultValidators) (lite.FullCommit, error) {
|
||||
// now get the commits and build a full commit
|
||||
commit, err := p.node.Commit(&vals.BlockHeight)
|
||||
func (p *provider) getValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
|
||||
if chainID != p.chainID {
|
||||
err = fmt.Errorf("expected chainID %s, got %s", p.chainID, chainID)
|
||||
return
|
||||
}
|
||||
if height < 1 {
|
||||
err = fmt.Errorf("expected height >= 1, got height %v", height)
|
||||
return
|
||||
}
|
||||
res, err := p.client.Validators(&height)
|
||||
if err != nil {
|
||||
// TODO pass through other types of errors.
|
||||
return nil, lerr.ErrUnknownValidators(chainID, height)
|
||||
}
|
||||
valset = types.NewValidatorSet(res.Validators)
|
||||
return
|
||||
}
|
||||
|
||||
// This does no validation.
|
||||
func (p *provider) fillFullCommit(signedHeader types.SignedHeader) (fc lite.FullCommit, err error) {
|
||||
|
||||
// Get the validators.
|
||||
valset, err := p.getValidatorSet(signedHeader.ChainID, signedHeader.Height)
|
||||
if err != nil {
|
||||
return lite.FullCommit{}, err
|
||||
}
|
||||
fc := lite.NewFullCommit(
|
||||
CommitFromResult(commit),
|
||||
types.NewValidatorSet(vals.Validators),
|
||||
)
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (p *provider) seedFromCommit(commit *ctypes.ResultCommit) (fc lite.FullCommit, err error) {
|
||||
fc.Commit = CommitFromResult(commit)
|
||||
|
||||
// now get the proper validators
|
||||
vals, err := p.node.Validators(&commit.Header.Height)
|
||||
// Get the next validators.
|
||||
nextValset, err := p.getValidatorSet(signedHeader.ChainID, signedHeader.Height+1)
|
||||
if err != nil {
|
||||
return fc, err
|
||||
return lite.FullCommit{}, err
|
||||
}
|
||||
|
||||
// make sure they match the commit (as we cannot enforce height)
|
||||
vset := types.NewValidatorSet(vals.Validators)
|
||||
if !bytes.Equal(vset.Hash(), commit.Header.ValidatorsHash) {
|
||||
return fc, liteErr.ErrValidatorsChanged()
|
||||
}
|
||||
|
||||
p.updateHeight(commit.Header.Height)
|
||||
fc.Validators = vset
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (p *provider) updateHeight(h int64) {
|
||||
if h > p.lastHeight {
|
||||
p.lastHeight = h
|
||||
}
|
||||
return lite.NewFullCommit(signedHeader, valset, nextValset), nil
|
||||
}
|
||||
|
|
|
@ -1,63 +1,61 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/abci/example/kvstore"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
rpctest "github.com/tendermint/tendermint/rpc/test"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
node := rpctest.StartTendermint(app)
|
||||
|
||||
code := m.Run()
|
||||
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
cfg := rpctest.GetConfig()
|
||||
rpcAddr := cfg.RPC.ListenAddress
|
||||
genDoc, _ := types.GenesisDocFromFile(cfg.GenesisFile())
|
||||
genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
chainID := genDoc.ChainID
|
||||
p := NewHTTPProvider(rpcAddr)
|
||||
t.Log("chainID:", chainID)
|
||||
p := NewHTTPProvider(chainID, rpcAddr)
|
||||
require.NotNil(t, p)
|
||||
|
||||
// let it produce some blocks
|
||||
err := rpcclient.WaitForHeight(p.(*provider).node, 6, nil)
|
||||
err = rpcclient.WaitForHeight(p.(*provider).client, 6, nil)
|
||||
require.Nil(err)
|
||||
|
||||
// let's get the highest block
|
||||
seed, err := p.LatestCommit()
|
||||
fc, err := p.LatestFullCommit(chainID, 1, 1<<63-1)
|
||||
|
||||
require.Nil(err, "%+v", err)
|
||||
sh := seed.Height()
|
||||
vhash := seed.Header.ValidatorsHash
|
||||
sh := fc.Height()
|
||||
assert.True(sh < 5000)
|
||||
|
||||
// let's check this is valid somehow
|
||||
assert.Nil(seed.ValidateBasic(chainID))
|
||||
cert := lite.NewStaticCertifier(chainID, seed.Validators)
|
||||
assert.Nil(fc.ValidateFull(chainID))
|
||||
|
||||
// historical queries now work :)
|
||||
lower := sh - 5
|
||||
seed, err = p.GetByHeight(lower)
|
||||
fc, err = p.LatestFullCommit(chainID, lower, lower)
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Equal(lower, seed.Height())
|
||||
assert.Equal(lower, fc.Height())
|
||||
|
||||
// also get by hash (given the match)
|
||||
seed, err = p.GetByHash(vhash)
|
||||
require.Nil(err, "%+v", err)
|
||||
require.Equal(vhash, seed.Header.ValidatorsHash)
|
||||
err = cert.Certify(seed.Commit)
|
||||
assert.Nil(err, "%+v", err)
|
||||
|
||||
// get by hash fails without match
|
||||
seed, err = p.GetByHash([]byte("foobar"))
|
||||
assert.NotNil(err)
|
||||
assert.True(liteErr.IsCommitNotFoundErr(err))
|
||||
|
||||
// storing the seed silently ignored
|
||||
err = p.StoreCommit(seed)
|
||||
assert.Nil(err, "%+v", err)
|
||||
}
|
||||
|
|
142
lite/commit.go
142
lite/commit.go
|
@ -2,98 +2,86 @@ package lite
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
// Certifier checks the votes to make sure the block really is signed properly.
|
||||
// Certifier must know the current set of validitors by some other means.
|
||||
type Certifier interface {
|
||||
Certify(check Commit) error
|
||||
ChainID() string
|
||||
}
|
||||
|
||||
// Commit is basically the rpc /commit response, but extended
|
||||
//
|
||||
// This is the basepoint for proving anything on the blockchain. It contains
|
||||
// a signed header. If the signatures are valid and > 2/3 of the known set,
|
||||
// we can store this checkpoint and use it to prove any number of aspects of
|
||||
// the system: such as txs, abci state, validator sets, etc...
|
||||
type Commit types.SignedHeader
|
||||
|
||||
// FullCommit is a commit and the actual validator set,
|
||||
// the base info you need to update to a given point,
|
||||
// assuming knowledge of some previous validator set
|
||||
// FullCommit is a signed header (the block header and a commit that signs it),
|
||||
// the validator set which signed the commit, and the next validator set. The
|
||||
// next validator set (which is proven from the block header) allows us to
|
||||
// revert to block-by-block updating of lite Verifier's latest validator set,
|
||||
// even in the face of arbitrarily large power changes.
|
||||
type FullCommit struct {
|
||||
Commit `json:"commit"`
|
||||
Validators *types.ValidatorSet `json:"validator_set"`
|
||||
SignedHeader types.SignedHeader `json:"signed_header"`
|
||||
Validators *types.ValidatorSet `json:"validator_set"`
|
||||
NextValidators *types.ValidatorSet `json:"next_validator_set"`
|
||||
}
|
||||
|
||||
// NewFullCommit returns a new FullCommit.
|
||||
func NewFullCommit(commit Commit, vals *types.ValidatorSet) FullCommit {
|
||||
func NewFullCommit(signedHeader types.SignedHeader, valset, nextValset *types.ValidatorSet) FullCommit {
|
||||
return FullCommit{
|
||||
Commit: commit,
|
||||
Validators: vals,
|
||||
SignedHeader: signedHeader,
|
||||
Validators: valset,
|
||||
NextValidators: nextValset,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the components and check for consistency.
|
||||
// This also checks to make sure that Validators actually
|
||||
// signed the SignedHeader.Commit.
|
||||
// If > 2/3 did not sign the Commit from fc.Validators, it
|
||||
// is not a valid commit!
|
||||
func (fc FullCommit) ValidateFull(chainID string) error {
|
||||
// Ensure that Validators exists and matches the header.
|
||||
if fc.Validators.Size() == 0 {
|
||||
return errors.New("need FullCommit.Validators")
|
||||
}
|
||||
if !bytes.Equal(
|
||||
fc.SignedHeader.ValidatorsHash,
|
||||
fc.Validators.Hash()) {
|
||||
return fmt.Errorf("header has vhash %X but valset hash is %X",
|
||||
fc.SignedHeader.ValidatorsHash,
|
||||
fc.Validators.Hash(),
|
||||
)
|
||||
}
|
||||
// Ensure that NextValidators exists and matches the header.
|
||||
if fc.NextValidators.Size() == 0 {
|
||||
return errors.New("need FullCommit.NextValidators")
|
||||
}
|
||||
if !bytes.Equal(
|
||||
fc.SignedHeader.NextValidatorsHash,
|
||||
fc.NextValidators.Hash()) {
|
||||
return fmt.Errorf("header has next vhash %X but next valset hash is %X",
|
||||
fc.SignedHeader.NextValidatorsHash,
|
||||
fc.NextValidators.Hash(),
|
||||
)
|
||||
}
|
||||
// Validate the header.
|
||||
err := fc.SignedHeader.ValidateBasic(chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Validate the signatures on the commit.
|
||||
hdr, cmt := fc.SignedHeader.Header, fc.SignedHeader.Commit
|
||||
return fc.Validators.VerifyCommit(
|
||||
hdr.ChainID, cmt.BlockID,
|
||||
hdr.Height, cmt)
|
||||
}
|
||||
|
||||
// Height returns the height of the header.
|
||||
func (c Commit) Height() int64 {
|
||||
if c.Header == nil {
|
||||
return 0
|
||||
func (fc FullCommit) Height() int64 {
|
||||
if fc.SignedHeader.Header == nil {
|
||||
panic("should not happen")
|
||||
}
|
||||
return c.Header.Height
|
||||
return fc.SignedHeader.Height
|
||||
}
|
||||
|
||||
// ValidatorsHash returns the hash of the validator set.
|
||||
func (c Commit) ValidatorsHash() []byte {
|
||||
if c.Header == nil {
|
||||
return nil
|
||||
// ChainID returns the chainID of the header.
|
||||
func (fc FullCommit) ChainID() string {
|
||||
if fc.SignedHeader.Header == nil {
|
||||
panic("should not happen")
|
||||
}
|
||||
return c.Header.ValidatorsHash
|
||||
}
|
||||
|
||||
// ValidateBasic does basic consistency checks and makes sure the headers
|
||||
// and commits are all consistent and refer to our chain.
|
||||
//
|
||||
// Make sure to use a Verifier to validate the signatures actually provide
|
||||
// a significantly strong proof for this header's validity.
|
||||
func (c Commit) ValidateBasic(chainID string) error {
|
||||
// make sure the header is reasonable
|
||||
if c.Header == nil {
|
||||
return errors.New("Commit missing header")
|
||||
}
|
||||
if c.Header.ChainID != chainID {
|
||||
return errors.Errorf("Header belongs to another chain '%s' not '%s'",
|
||||
c.Header.ChainID, chainID)
|
||||
}
|
||||
|
||||
if c.Commit == nil {
|
||||
return errors.New("Commit missing signatures")
|
||||
}
|
||||
|
||||
// make sure the header and commit match (height and hash)
|
||||
if c.Commit.Height() != c.Header.Height {
|
||||
return liteErr.ErrHeightMismatch(c.Commit.Height(), c.Header.Height)
|
||||
}
|
||||
hhash := c.Header.Hash()
|
||||
chash := c.Commit.BlockID.Hash
|
||||
if !bytes.Equal(hhash, chash) {
|
||||
return errors.Errorf("Commits sign block %X header is block %X",
|
||||
chash, hhash)
|
||||
}
|
||||
|
||||
// make sure the commit is reasonable
|
||||
err := c.Commit.ValidateBasic()
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// looks good, we just need to make sure the signatures are really from
|
||||
// empowered validators
|
||||
return nil
|
||||
return fc.SignedHeader.ChainID
|
||||
}
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
type DBProvider struct {
|
||||
logger log.Logger
|
||||
label string
|
||||
db dbm.DB
|
||||
cdc *amino.Codec
|
||||
limit int
|
||||
}
|
||||
|
||||
func NewDBProvider(label string, db dbm.DB) *DBProvider {
|
||||
|
||||
// NOTE: when debugging, this type of construction might be useful.
|
||||
//db = dbm.NewDebugDB("db provider "+cmn.RandStr(4), db)
|
||||
|
||||
cdc := amino.NewCodec()
|
||||
cryptoAmino.RegisterAmino(cdc)
|
||||
dbp := &DBProvider{
|
||||
logger: log.NewNopLogger(),
|
||||
label: label,
|
||||
db: db,
|
||||
cdc: cdc,
|
||||
}
|
||||
return dbp
|
||||
}
|
||||
|
||||
func (dbp *DBProvider) SetLogger(logger log.Logger) {
|
||||
dbp.logger = logger.With("label", dbp.label)
|
||||
}
|
||||
|
||||
func (dbp *DBProvider) SetLimit(limit int) *DBProvider {
|
||||
dbp.limit = limit
|
||||
return dbp
|
||||
}
|
||||
|
||||
// Implements PersistentProvider.
|
||||
func (dbp *DBProvider) SaveFullCommit(fc FullCommit) error {
|
||||
|
||||
dbp.logger.Info("DBProvider.SaveFullCommit()...", "fc", fc)
|
||||
batch := dbp.db.NewBatch()
|
||||
|
||||
// Save the fc.validators.
|
||||
// We might be overwriting what we already have, but
|
||||
// it makes the logic easier for now.
|
||||
vsKey := validatorSetKey(fc.ChainID(), fc.Height())
|
||||
vsBz, err := dbp.cdc.MarshalBinary(fc.Validators)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
batch.Set(vsKey, vsBz)
|
||||
|
||||
// Save the fc.NextValidators.
|
||||
nvsKey := validatorSetKey(fc.ChainID(), fc.Height()+1)
|
||||
nvsBz, err := dbp.cdc.MarshalBinary(fc.NextValidators)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
batch.Set(nvsKey, nvsBz)
|
||||
|
||||
// Save the fc.SignedHeader
|
||||
shKey := signedHeaderKey(fc.ChainID(), fc.Height())
|
||||
shBz, err := dbp.cdc.MarshalBinary(fc.SignedHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
batch.Set(shKey, shBz)
|
||||
|
||||
// And write sync.
|
||||
batch.WriteSync()
|
||||
|
||||
// Garbage collect.
|
||||
// TODO: optimize later.
|
||||
if dbp.limit > 0 {
|
||||
dbp.deleteAfterN(fc.ChainID(), dbp.limit)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements Provider.
|
||||
func (dbp *DBProvider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (
|
||||
FullCommit, error) {
|
||||
|
||||
dbp.logger.Info("DBProvider.LatestFullCommit()...",
|
||||
"chainID", chainID, "minHeight", minHeight, "maxHeight", maxHeight)
|
||||
|
||||
if minHeight <= 0 {
|
||||
minHeight = 1
|
||||
}
|
||||
if maxHeight == 0 {
|
||||
maxHeight = 1<<63 - 1
|
||||
}
|
||||
|
||||
itr := dbp.db.ReverseIterator(
|
||||
signedHeaderKey(chainID, maxHeight),
|
||||
signedHeaderKey(chainID, minHeight-1),
|
||||
)
|
||||
defer itr.Close()
|
||||
|
||||
for itr.Valid() {
|
||||
key := itr.Key()
|
||||
_, _, ok := parseSignedHeaderKey(key)
|
||||
if !ok {
|
||||
// Skip over other keys.
|
||||
itr.Next()
|
||||
continue
|
||||
} else {
|
||||
// Found the latest full commit signed header.
|
||||
shBz := itr.Value()
|
||||
sh := types.SignedHeader{}
|
||||
err := dbp.cdc.UnmarshalBinary(shBz, &sh)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
} else {
|
||||
lfc, err := dbp.fillFullCommit(sh)
|
||||
if err == nil {
|
||||
dbp.logger.Info("DBProvider.LatestFullCommit() found latest.", "height", lfc.Height())
|
||||
return lfc, nil
|
||||
} else {
|
||||
dbp.logger.Error("DBProvider.LatestFullCommit() got error", "lfc", lfc)
|
||||
dbp.logger.Error(fmt.Sprintf("%+v", err))
|
||||
return lfc, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FullCommit{}, lerr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
func (dbp *DBProvider) ValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
|
||||
return dbp.getValidatorSet(chainID, height)
|
||||
}
|
||||
|
||||
func (dbp *DBProvider) getValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
|
||||
vsBz := dbp.db.Get(validatorSetKey(chainID, height))
|
||||
if vsBz == nil {
|
||||
err = lerr.ErrUnknownValidators(chainID, height)
|
||||
return
|
||||
}
|
||||
err = dbp.cdc.UnmarshalBinary(vsBz, &valset)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// To test deep equality. This makes it easier to test for e.g. valset
|
||||
// equivalence using assert.Equal (tests for deep equality) in our tests,
|
||||
// which also tests for unexported/private field equivalence.
|
||||
valset.TotalVotingPower()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (dbp *DBProvider) fillFullCommit(sh types.SignedHeader) (FullCommit, error) {
|
||||
var chainID = sh.ChainID
|
||||
var height = sh.Height
|
||||
var valset, nextValset *types.ValidatorSet
|
||||
// Load the validator set.
|
||||
valset, err := dbp.getValidatorSet(chainID, height)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
// Load the next validator set.
|
||||
nextValset, err = dbp.getValidatorSet(chainID, height+1)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
// Return filled FullCommit.
|
||||
return FullCommit{
|
||||
SignedHeader: sh,
|
||||
Validators: valset,
|
||||
NextValidators: nextValset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dbp *DBProvider) deleteAfterN(chainID string, after int) error {
|
||||
|
||||
dbp.logger.Info("DBProvider.deleteAfterN()...", "chainID", chainID, "after", after)
|
||||
|
||||
itr := dbp.db.ReverseIterator(
|
||||
signedHeaderKey(chainID, 1<<63-1),
|
||||
signedHeaderKey(chainID, 0),
|
||||
)
|
||||
defer itr.Close()
|
||||
|
||||
var lastHeight int64 = 1<<63 - 1
|
||||
var numSeen = 0
|
||||
var numDeleted = 0
|
||||
|
||||
for itr.Valid() {
|
||||
key := itr.Key()
|
||||
_, height, ok := parseChainKeyPrefix(key)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected key %v", key)
|
||||
} else {
|
||||
if height < lastHeight {
|
||||
lastHeight = height
|
||||
numSeen += 1
|
||||
}
|
||||
if numSeen > after {
|
||||
dbp.db.Delete(key)
|
||||
numDeleted += 1
|
||||
}
|
||||
}
|
||||
itr.Next()
|
||||
}
|
||||
|
||||
dbp.logger.Info(fmt.Sprintf("DBProvider.deleteAfterN() deleted %v items", numDeleted))
|
||||
return nil
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// key encoding
|
||||
|
||||
func signedHeaderKey(chainID string, height int64) []byte {
|
||||
return []byte(fmt.Sprintf("%s/%010d/sh", chainID, height))
|
||||
}
|
||||
|
||||
func validatorSetKey(chainID string, height int64) []byte {
|
||||
return []byte(fmt.Sprintf("%s/%010d/vs", chainID, height))
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// key parsing
|
||||
|
||||
var keyPattern = regexp.MustCompile(`^([^/]+)/([0-9]*)/(.*)$`)
|
||||
|
||||
func parseKey(key []byte) (chainID string, height int64, part string, ok bool) {
|
||||
submatch := keyPattern.FindSubmatch(key)
|
||||
if submatch == nil {
|
||||
return "", 0, "", false
|
||||
}
|
||||
chainID = string(submatch[1])
|
||||
heightStr := string(submatch[2])
|
||||
heightInt, err := strconv.Atoi(heightStr)
|
||||
if err != nil {
|
||||
return "", 0, "", false
|
||||
}
|
||||
height = int64(heightInt)
|
||||
part = string(submatch[3])
|
||||
ok = true // good!
|
||||
return
|
||||
}
|
||||
|
||||
func parseSignedHeaderKey(key []byte) (chainID string, height int64, ok bool) {
|
||||
chainID, height, part, ok := parseKey(key)
|
||||
if part != "sh" {
|
||||
return "", 0, false
|
||||
}
|
||||
return chainID, height, true
|
||||
}
|
||||
|
||||
func parseChainKeyPrefix(key []byte) (chainID string, height int64, ok bool) {
|
||||
chainID, height, _, ok = parseKey(key)
|
||||
return chainID, height, true
|
||||
}
|
211
lite/doc.go
211
lite/doc.go
|
@ -1,133 +1,140 @@
|
|||
/*
|
||||
Package lite allows you to securely validate headers
|
||||
without a full node.
|
||||
Package lite allows you to securely validate headers without a full node.
|
||||
|
||||
This library pulls together all the crypto and algorithms,
|
||||
so given a relatively recent (< unbonding period) known
|
||||
validator set, one can get indisputable proof that data is in
|
||||
the chain (current state) or detect if the node is lying to
|
||||
the client.
|
||||
This library pulls together all the crypto and algorithms, so given a
|
||||
relatively recent (< unbonding period) known validator set, one can get
|
||||
indisputable proof that data is in the chain (current state) or detect if the
|
||||
node is lying to the client.
|
||||
|
||||
Tendermint RPC exposes a lot of info, but a malicious node
|
||||
could return any data it wants to queries, or even to block
|
||||
headers, even making up fake signatures from non-existent
|
||||
validators to justify it. This is a lot of logic to get
|
||||
right, to be contained in a small, easy to use library,
|
||||
that does this for you, so you can just build nice UI.
|
||||
Tendermint RPC exposes a lot of info, but a malicious node could return any
|
||||
data it wants to queries, or even to block headers, even making up fake
|
||||
signatures from non-existent validators to justify it. This is a lot of logic
|
||||
to get right, to be contained in a small, easy to use library, that does this
|
||||
for you, so you can just build nice applications.
|
||||
|
||||
We design for clients who have no strong trust relationship
|
||||
with any tendermint node, just the validator set as a whole.
|
||||
Beyond building nice mobile or desktop applications, the
|
||||
cosmos hub is another important example of a client,
|
||||
that needs undeniable proof without syncing the full chain,
|
||||
in order to efficiently implement IBC.
|
||||
We design for clients who have no strong trust relationship with any Tendermint
|
||||
node, just the blockchain and validator set as a whole.
|
||||
|
||||
Commits
|
||||
# Data structures
|
||||
|
||||
There are two main data structures that we pass around - Commit
|
||||
and FullCommit. Both of them mirror what information is
|
||||
exposed in tendermint rpc.
|
||||
## SignedHeader
|
||||
|
||||
Commit is a block header along with enough validator signatures
|
||||
to prove its validity (> 2/3 of the voting power). A FullCommit
|
||||
is a Commit along with the full validator set. When the
|
||||
validator set doesn't change, the Commit is enough, but since
|
||||
the block header only has a hash, we need the FullCommit to
|
||||
follow any changes to the validator set.
|
||||
SignedHeader is a block header along with a commit -- enough validator
|
||||
precommit-vote signatures to prove its validity (> 2/3 of the voting power)
|
||||
given the validator set responsible for signing that header. A FullCommit is a
|
||||
SignedHeader along with the current and next validator sets.
|
||||
|
||||
Certifiers
|
||||
The hash of the next validator set is included and signed in the SignedHeader.
|
||||
This lets the lite client keep track of arbitrary changes to the validator set,
|
||||
as every change to the validator set must be approved by inclusion in the
|
||||
header and signed in the commit.
|
||||
|
||||
A Certifier validates a new Commit given the currently known
|
||||
state. There are three different types of Certifiers exposed,
|
||||
each one building on the last one, with additional complexity.
|
||||
In the worst case, with every block changing the validators around completely,
|
||||
a lite client can sync up with every block header to verify each validator set
|
||||
change on the chain. In practice, most applications will not have frequent
|
||||
drastic updates to the validator set, so the logic defined in this package for
|
||||
lite client syncing is optimized to use intelligent bisection and
|
||||
block-skipping for efficient sourcing and verification of these data structures
|
||||
and updates to the validator set (see the DynamicVerifier for more
|
||||
information).
|
||||
|
||||
Static - given the validator set upon initialization. Verifies
|
||||
all signatures against that set and if the validator set
|
||||
changes, it will reject all headers.
|
||||
The FullCommit is also declared in this package as a convenience structure,
|
||||
which includes the SignedHeader along with the full current and next
|
||||
ValidatorSets.
|
||||
|
||||
Dynamic - This wraps Static and has the same Certify
|
||||
method. However, it adds an Update method, which can be called
|
||||
with a FullCommit when the validator set changes. If it can
|
||||
prove this is a valid transition, it will update the validator
|
||||
set.
|
||||
## Verifier
|
||||
|
||||
Inquiring - this wraps Dynamic and implements an auto-update
|
||||
strategy on top of the Dynamic update. If a call to
|
||||
Certify fails as the validator set has changed, then it
|
||||
attempts to find a FullCommit and Update to that header.
|
||||
To get these FullCommits, it makes use of a Provider.
|
||||
A Verifier validates a new SignedHeader given the currently known state. There
|
||||
are two different types of Verifiers provided.
|
||||
|
||||
Providers
|
||||
BaseVerifier - given a validator set and a height, this Verifier verifies
|
||||
that > 2/3 of the voting power of the given validator set had signed the
|
||||
SignedHeader, and that the SignedHeader was to be signed by the exact given
|
||||
validator set, and that the height of the commit is at least height (or
|
||||
greater).
|
||||
|
||||
A Provider allows us to store and retrieve the FullCommits,
|
||||
to provide memory to the Inquiring Certifier.
|
||||
SignedHeader.Commit may be signed by a different validator set, it can get
|
||||
certified with a BaseVerifier as long as sufficient signatures from the
|
||||
previous validator set are present in the commit.
|
||||
|
||||
NewMemStoreProvider - in-memory cache.
|
||||
DynamicVerifier - this Verifier implements an auto-update and persistence
|
||||
strategy to certify any SignedHeader of the blockchain.
|
||||
|
||||
files.NewProvider - disk backed storage.
|
||||
## Provider and PersistentProvider
|
||||
|
||||
client.NewHTTPProvider - query tendermint rpc.
|
||||
A Provider allows us to store and retrieve the FullCommits.
|
||||
|
||||
NewCacheProvider - combine multiple providers.
|
||||
```go
|
||||
type Provider interface {
|
||||
// LatestFullCommit returns the latest commit with
|
||||
// minHeight <= height <= maxHeight.
|
||||
// If maxHeight is zero, returns the latest where
|
||||
// minHeight <= height.
|
||||
LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error)
|
||||
}
|
||||
```
|
||||
|
||||
The suggested use for local light clients is
|
||||
client.NewHTTPProvider for getting new data (Source),
|
||||
and NewCacheProvider(NewMemStoreProvider(),
|
||||
files.NewProvider()) to store confirmed headers (Trusted)
|
||||
* client.NewHTTPProvider - query Tendermint rpc.
|
||||
|
||||
How We Track Validators
|
||||
A PersistentProvider is a Provider that also allows for saving state. This is
|
||||
used by the DynamicVerifier for persistence.
|
||||
|
||||
Unless you want to blindly trust the node you talk with, you
|
||||
need to trace every response back to a hash in a block header
|
||||
and validate the commit signatures of that block header match
|
||||
the proper validator set. If there is a contant validator
|
||||
set, you store it locally upon initialization of the client,
|
||||
```go
|
||||
type PersistentProvider interface {
|
||||
Provider
|
||||
|
||||
// SaveFullCommit saves a FullCommit (without verification).
|
||||
SaveFullCommit(fc FullCommit) error
|
||||
}
|
||||
```
|
||||
|
||||
* DBProvider - persistence provider for use with any tmlibs/DB.
|
||||
* MultiProvider - combine multiple providers.
|
||||
|
||||
The suggested use for local light clients is client.NewHTTPProvider(...) for
|
||||
getting new data (Source), and NewMultiProvider(NewDBProvider("label",
|
||||
dbm.NewMemDB()), NewDBProvider("label", db.NewFileDB(...))) to store confirmed
|
||||
full commits (Trusted)
|
||||
|
||||
|
||||
# How We Track Validators
|
||||
|
||||
Unless you want to blindly trust the node you talk with, you need to trace
|
||||
every response back to a hash in a block header and validate the commit
|
||||
signatures of that block header match the proper validator set. If there is a
|
||||
static validator set, you store it locally upon initialization of the client,
|
||||
and check against that every time.
|
||||
|
||||
Once there is a dynamic validator set, the issue of
|
||||
verifying a block becomes a bit more tricky. There is
|
||||
background information in a
|
||||
github issue (https://github.com/tendermint/tendermint/issues/377).
|
||||
If the validator set for the blockchain is dynamic, verifying block commits is
|
||||
a bit more involved -- if there is a block at height H with a known (trusted)
|
||||
validator set V, and another block at height H' (H' > H) with validator set V'
|
||||
!= V, then we want a way to safely update it.
|
||||
|
||||
In short, if there is a block at height H with a known
|
||||
(trusted) validator set V, and another block at height H'
|
||||
(H' > H) with validator set V' != V, then we want a way to
|
||||
safely update it.
|
||||
First, we get the new (unconfirmed) validator set V' and verify that H' is
|
||||
internally consistent and properly signed by this V'. Assuming it is a valid
|
||||
block, we check that at least 2/3 of the validators in V also signed it,
|
||||
meaning it would also be valid under our old assumptions. Then, we accept H'
|
||||
and V' as valid and trusted and use that to validate for heights X > H' until a
|
||||
more recent and updated validator set is found.
|
||||
|
||||
First, get the new (unconfirmed) validator set V' and
|
||||
verify H' is internally consistent and properly signed by
|
||||
this V'. Assuming it is a valid block, we check that at
|
||||
least 2/3 of the validators in V also signed it, meaning
|
||||
it would also be valid under our old assumptions.
|
||||
That should be enough, but we can also check that the
|
||||
V counts for at least 2/3 of the total votes in H'
|
||||
for extra safety (we can have a discussion if this is
|
||||
strictly required). If we can verify all this,
|
||||
then we can accept H' and V' as valid and use that to
|
||||
validate all blocks X > H'.
|
||||
If we cannot update directly from H -> H' because there was too much change to
|
||||
the validator set, then we can look for some Hm (H < Hm < H') with a validator
|
||||
set Vm. Then we try to update H -> Hm and then Hm -> H' in two steps. If one
|
||||
of these steps doesn't work, then we continue bisecting, until we eventually
|
||||
have to externally validate the valdiator set changes at every block.
|
||||
|
||||
If we cannot update directly from H -> H' because there was
|
||||
too much change to the validator set, then we can look for
|
||||
some Hm (H < Hm < H') with a validator set Vm. Then we try
|
||||
to update H -> Hm and Hm -> H' in two separate steps.
|
||||
If one of these steps doesn't work, then we continue
|
||||
bisecting, until we eventually have to externally
|
||||
validate the valdiator set changes at every block.
|
||||
Since we never trust any server in this protocol, only the signatures
|
||||
themselves, it doesn't matter if the seed comes from a (possibly malicious)
|
||||
node or a (possibly malicious) user. We can accept it or reject it based only
|
||||
on our trusted validator set and cryptographic proofs. This makes it extremely
|
||||
important to verify that you have the proper validator set when initializing
|
||||
the client, as that is the root of all trust.
|
||||
|
||||
Since we never trust any server in this protocol, only the
|
||||
signatures themselves, it doesn't matter if the seed comes
|
||||
from a (possibly malicious) node or a (possibly malicious) user.
|
||||
We can accept it or reject it based only on our trusted
|
||||
validator set and cryptographic proofs. This makes it
|
||||
extremely important to verify that you have the proper
|
||||
validator set when initializing the client, as that is the
|
||||
root of all trust.
|
||||
The software currently assumes that the unbonding period is infinite in
|
||||
duration. If the DynamicVerifier hasn't been updated in a while, you should
|
||||
manually verify the block headers using other sources.
|
||||
|
||||
Or course, this assumes that the known block is within the
|
||||
unbonding period to avoid the "nothing at stake" problem.
|
||||
If you haven't seen the state in a few months, you will need
|
||||
to manually verify the new validator set hash using off-chain
|
||||
means (the same as getting the initial hash).
|
||||
TODO: Update the software to handle cases around the unbonding period.
|
||||
|
||||
*/
|
||||
package lite
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
var _ Certifier = (*DynamicCertifier)(nil)
|
||||
|
||||
// DynamicCertifier uses a StaticCertifier for Certify, but adds an
|
||||
// Update method to allow for a change of validators.
|
||||
//
|
||||
// You can pass in a FullCommit with another validator set,
|
||||
// and if this is a provably secure transition (< 1/3 change,
|
||||
// sufficient signatures), then it will update the
|
||||
// validator set for the next Certify call.
|
||||
// For security, it will only follow validator set changes
|
||||
// going forward.
|
||||
type DynamicCertifier struct {
|
||||
cert *StaticCertifier
|
||||
lastHeight int64
|
||||
}
|
||||
|
||||
// NewDynamic returns a new dynamic certifier.
|
||||
func NewDynamicCertifier(chainID string, vals *types.ValidatorSet, height int64) *DynamicCertifier {
|
||||
return &DynamicCertifier{
|
||||
cert: NewStaticCertifier(chainID, vals),
|
||||
lastHeight: height,
|
||||
}
|
||||
}
|
||||
|
||||
// ChainID returns the chain id of this certifier.
|
||||
// Implements Certifier.
|
||||
func (dc *DynamicCertifier) ChainID() string {
|
||||
return dc.cert.ChainID()
|
||||
}
|
||||
|
||||
// Validators returns the validators of this certifier.
|
||||
func (dc *DynamicCertifier) Validators() *types.ValidatorSet {
|
||||
return dc.cert.vSet
|
||||
}
|
||||
|
||||
// Hash returns the hash of this certifier.
|
||||
func (dc *DynamicCertifier) Hash() []byte {
|
||||
return dc.cert.Hash()
|
||||
}
|
||||
|
||||
// LastHeight returns the last height of this certifier.
|
||||
func (dc *DynamicCertifier) LastHeight() int64 {
|
||||
return dc.lastHeight
|
||||
}
|
||||
|
||||
// Certify will verify whether the commit is valid and will update the height if it is or return an
|
||||
// error if it is not.
|
||||
// Implements Certifier.
|
||||
func (dc *DynamicCertifier) Certify(check Commit) error {
|
||||
err := dc.cert.Certify(check)
|
||||
if err == nil {
|
||||
// update last seen height if input is valid
|
||||
dc.lastHeight = check.Height()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Update will verify if this is a valid change and update
|
||||
// the certifying validator set if safe to do so.
|
||||
//
|
||||
// Returns an error if update is impossible (invalid proof or IsTooMuchChangeErr)
|
||||
func (dc *DynamicCertifier) Update(fc FullCommit) error {
|
||||
// ignore all checkpoints in the past -> only to the future
|
||||
h := fc.Height()
|
||||
if h <= dc.lastHeight {
|
||||
return liteErr.ErrPastTime()
|
||||
}
|
||||
|
||||
// first, verify if the input is self-consistent....
|
||||
err := fc.ValidateBasic(dc.ChainID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// now, make sure not too much change... meaning this commit
|
||||
// would be approved by the currently known validator set
|
||||
// as well as the new set
|
||||
commit := fc.Commit.Commit
|
||||
err = dc.Validators().VerifyCommitAny(fc.Validators, dc.ChainID(), commit.BlockID, h, commit)
|
||||
if err != nil {
|
||||
return liteErr.ErrTooMuchChange()
|
||||
}
|
||||
|
||||
// looks good, we can update
|
||||
dc.cert = NewStaticCertifier(dc.ChainID(), fc.Validators)
|
||||
dc.lastHeight = h
|
||||
return nil
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
package lite_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
"github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
// TestDynamicCert just makes sure it still works like StaticCert
|
||||
func TestDynamicCert(t *testing.T) {
|
||||
// assert, require := assert.New(t), require.New(t)
|
||||
assert := assert.New(t)
|
||||
// require := require.New(t)
|
||||
|
||||
keys := lite.GenValKeys(4)
|
||||
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
||||
vals := keys.ToValidators(20, 10)
|
||||
// and a certifier based on our known set
|
||||
chainID := "test-dyno"
|
||||
cert := lite.NewDynamicCertifier(chainID, vals, 0)
|
||||
|
||||
cases := []struct {
|
||||
keys lite.ValKeys
|
||||
vals *types.ValidatorSet
|
||||
height int64
|
||||
first, last int // who actually signs
|
||||
proper bool // true -> expect no error
|
||||
changed bool // true -> expect validator change error
|
||||
}{
|
||||
// perfect, signed by everyone
|
||||
{keys, vals, 1, 0, len(keys), true, false},
|
||||
// skip little guy is okay
|
||||
{keys, vals, 2, 1, len(keys), true, false},
|
||||
// but not the big guy
|
||||
{keys, vals, 3, 0, len(keys) - 1, false, false},
|
||||
// even changing the power a little bit breaks the static validator
|
||||
// the sigs are enough, but the validator hash is unknown
|
||||
{keys, keys.ToValidators(20, 11), 4, 0, len(keys), false, true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
check := tc.keys.GenCommit(chainID, tc.height, nil, tc.vals,
|
||||
[]byte("bar"), []byte("params"), []byte("results"), tc.first, tc.last)
|
||||
err := cert.Certify(check)
|
||||
if tc.proper {
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Equal(cert.LastHeight(), tc.height)
|
||||
} else {
|
||||
assert.NotNil(err)
|
||||
if tc.changed {
|
||||
assert.True(errors.IsValidatorsChangedErr(err), "%+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDynamicUpdate makes sure we update safely and sanely
|
||||
func TestDynamicUpdate(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
chainID := "test-dyno-up"
|
||||
keys := lite.GenValKeys(5)
|
||||
vals := keys.ToValidators(20, 0)
|
||||
cert := lite.NewDynamicCertifier(chainID, vals, 40)
|
||||
|
||||
// one valid block to give us a sense of time
|
||||
h := int64(100)
|
||||
good := keys.GenCommit(chainID, h, nil, vals, []byte("foo"), []byte("params"), []byte("results"), 0, len(keys))
|
||||
err := cert.Certify(good)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// some new sets to try later
|
||||
keys2 := keys.Extend(2)
|
||||
keys3 := keys2.Extend(4)
|
||||
|
||||
// we try to update with some blocks
|
||||
cases := []struct {
|
||||
keys lite.ValKeys
|
||||
vals *types.ValidatorSet
|
||||
height int64
|
||||
first, last int // who actually signs
|
||||
proper bool // true -> expect no error
|
||||
changed bool // true -> expect too much change error
|
||||
}{
|
||||
// same validator set, well signed, of course it is okay
|
||||
{keys, vals, h + 10, 0, len(keys), true, false},
|
||||
// same validator set, poorly signed, fails
|
||||
{keys, vals, h + 20, 2, len(keys), false, false},
|
||||
|
||||
// shift the power a little, works if properly signed
|
||||
{keys, keys.ToValidators(10, 0), h + 30, 1, len(keys), true, false},
|
||||
// but not on a poor signature
|
||||
{keys, keys.ToValidators(10, 0), h + 40, 2, len(keys), false, false},
|
||||
// and not if it was in the past
|
||||
{keys, keys.ToValidators(10, 0), h + 25, 0, len(keys), false, false},
|
||||
|
||||
// let's try to adjust to a whole new validator set (we have 5/7 of the votes)
|
||||
{keys2, keys2.ToValidators(10, 0), h + 33, 0, len(keys2), true, false},
|
||||
|
||||
// properly signed but too much change, not allowed (only 7/11 validators known)
|
||||
{keys3, keys3.ToValidators(10, 0), h + 50, 0, len(keys3), false, true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
fc := tc.keys.GenFullCommit(chainID, tc.height, nil, tc.vals,
|
||||
[]byte("bar"), []byte("params"), []byte("results"), tc.first, tc.last)
|
||||
err := cert.Update(fc)
|
||||
if tc.proper {
|
||||
assert.Nil(err, "%d: %+v", tc.height, err)
|
||||
// we update last seen height
|
||||
assert.Equal(cert.LastHeight(), tc.height)
|
||||
// and we update the proper validators
|
||||
assert.EqualValues(fc.Header.ValidatorsHash, cert.Hash())
|
||||
} else {
|
||||
assert.NotNil(err, "%d", tc.height)
|
||||
// we don't update the height
|
||||
assert.NotEqual(cert.LastHeight(), tc.height)
|
||||
if tc.changed {
|
||||
assert.True(errors.IsTooMuchChangeErr(err),
|
||||
"%d: %+v", tc.height, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var _ Verifier = (*DynamicVerifier)(nil)
|
||||
|
||||
// DynamicVerifier implements an auto-updating Verifier. It uses a
|
||||
// "source" provider to obtain the needed FullCommits to securely sync with
|
||||
// validator set changes. It stores properly validated data on the
|
||||
// "trusted" local system.
|
||||
type DynamicVerifier struct {
|
||||
logger log.Logger
|
||||
chainID string
|
||||
// These are only properly validated data, from local system.
|
||||
trusted PersistentProvider
|
||||
// This is a source of new info, like a node rpc, or other import method.
|
||||
source Provider
|
||||
}
|
||||
|
||||
// NewDynamicVerifier returns a new DynamicVerifier. It uses the
|
||||
// trusted provider to store validated data and the source provider to
|
||||
// obtain missing data (e.g. FullCommits).
|
||||
//
|
||||
// The trusted provider should a CacheProvider, MemProvider or
|
||||
// files.Provider. The source provider should be a client.HTTPProvider.
|
||||
func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier {
|
||||
return &DynamicVerifier{
|
||||
logger: log.NewNopLogger(),
|
||||
chainID: chainID,
|
||||
trusted: trusted,
|
||||
source: source,
|
||||
}
|
||||
}
|
||||
|
||||
func (ic *DynamicVerifier) SetLogger(logger log.Logger) {
|
||||
logger = logger.With("module", "lite")
|
||||
ic.logger = logger
|
||||
ic.trusted.SetLogger(logger)
|
||||
ic.source.SetLogger(logger)
|
||||
}
|
||||
|
||||
// Implements Verifier.
|
||||
func (ic *DynamicVerifier) ChainID() string {
|
||||
return ic.chainID
|
||||
}
|
||||
|
||||
// Implements Verifier.
|
||||
//
|
||||
// If the validators have changed since the last known time, it looks to
|
||||
// ic.trusted and ic.source to prove the new validators. On success, it will
|
||||
// try to store the SignedHeader in ic.trusted if the next
|
||||
// validator can be sourced.
|
||||
func (ic *DynamicVerifier) Certify(shdr types.SignedHeader) error {
|
||||
|
||||
// Get the latest known full commit <= h-1 from our trusted providers.
|
||||
// The full commit at h-1 contains the valset to sign for h.
|
||||
h := shdr.Height - 1
|
||||
trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if trustedFC.Height() == h {
|
||||
// Return error if valset doesn't match.
|
||||
if !bytes.Equal(
|
||||
trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash) {
|
||||
return lerr.ErrUnexpectedValidators(
|
||||
trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash)
|
||||
}
|
||||
} else {
|
||||
// If valset doesn't match...
|
||||
if !bytes.Equal(trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash) {
|
||||
// ... update.
|
||||
trustedFC, err = ic.updateToHeight(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Return error if valset _still_ doesn't match.
|
||||
if !bytes.Equal(trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash) {
|
||||
return lerr.ErrUnexpectedValidators(
|
||||
trustedFC.NextValidators.Hash(),
|
||||
shdr.Header.ValidatorsHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Certify the signed header using the matching valset.
|
||||
cert := NewBaseVerifier(ic.chainID, trustedFC.Height()+1, trustedFC.NextValidators)
|
||||
err = cert.Certify(shdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the next validator set.
|
||||
nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1)
|
||||
if lerr.IsErrUnknownValidators(err) {
|
||||
// Ignore this error.
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create filled FullCommit.
|
||||
nfc := FullCommit{
|
||||
SignedHeader: shdr,
|
||||
Validators: trustedFC.NextValidators,
|
||||
NextValidators: nextValset,
|
||||
}
|
||||
// Validate the full commit. This checks the cryptographic
|
||||
// signatures of Commit against Validators.
|
||||
if err := nfc.ValidateFull(ic.chainID); err != nil {
|
||||
return err
|
||||
}
|
||||
// Trust it.
|
||||
return ic.trusted.SaveFullCommit(nfc)
|
||||
}
|
||||
|
||||
// verifyAndSave will verify if this is a valid source full commit given the
|
||||
// best match trusted full commit, and if good, persist to ic.trusted.
|
||||
// Returns ErrTooMuchChange when >2/3 of trustedFC did not sign sourceFC.
|
||||
// Panics if trustedFC.Height() >= sourceFC.Height().
|
||||
func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error {
|
||||
if trustedFC.Height() >= sourceFC.Height() {
|
||||
panic("should not happen")
|
||||
}
|
||||
err := trustedFC.NextValidators.VerifyFutureCommit(
|
||||
sourceFC.Validators,
|
||||
ic.chainID, sourceFC.SignedHeader.Commit.BlockID,
|
||||
sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ic.trusted.SaveFullCommit(sourceFC)
|
||||
}
|
||||
|
||||
// updateToHeight will use divide-and-conquer to find a path to h.
|
||||
// Returns nil error iff we successfully verify and persist a full commit
|
||||
// for height h, using repeated applications of bisection if necessary.
|
||||
//
|
||||
// Returns ErrCommitNotFound if source provider doesn't have the commit for h.
|
||||
func (ic *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) {
|
||||
|
||||
// Fetch latest full commit from source.
|
||||
sourceFC, err := ic.source.LatestFullCommit(ic.chainID, h, h)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
|
||||
// Validate the full commit. This checks the cryptographic
|
||||
// signatures of Commit against Validators.
|
||||
if err := sourceFC.ValidateFull(ic.chainID); err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
|
||||
// If sourceFC.Height() != h, we can't do it.
|
||||
if sourceFC.Height() != h {
|
||||
return FullCommit{}, lerr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
FOR_LOOP:
|
||||
for {
|
||||
// Fetch latest full commit from trusted.
|
||||
trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
// We have nothing to do.
|
||||
if trustedFC.Height() == h {
|
||||
return trustedFC, nil
|
||||
}
|
||||
|
||||
// Try to update to full commit with checks.
|
||||
err = ic.verifyAndSave(trustedFC, sourceFC)
|
||||
if err == nil {
|
||||
// All good!
|
||||
return sourceFC, nil
|
||||
}
|
||||
|
||||
// Handle special case when err is ErrTooMuchChange.
|
||||
if lerr.IsErrTooMuchChange(err) {
|
||||
// Divide and conquer.
|
||||
start, end := trustedFC.Height(), sourceFC.Height()
|
||||
if !(start < end) {
|
||||
panic("should not happen")
|
||||
}
|
||||
mid := (start + end) / 2
|
||||
_, err = ic.updateToHeight(mid)
|
||||
if err != nil {
|
||||
return FullCommit{}, err
|
||||
}
|
||||
// If we made it to mid, we retry.
|
||||
continue FOR_LOOP
|
||||
}
|
||||
return FullCommit{}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (ic *DynamicVerifier) LastTrustedHeight() int64 {
|
||||
fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1)
|
||||
if err != nil {
|
||||
panic("should not happen")
|
||||
}
|
||||
return fc.Height()
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
func TestInquirerValidPath(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
trust := NewDBProvider("trust", dbm.NewMemDB())
|
||||
source := NewDBProvider("source", dbm.NewMemDB())
|
||||
|
||||
// Set up the validators to generate test blocks.
|
||||
var vote int64 = 10
|
||||
keys := genPrivKeys(5)
|
||||
nkeys := keys.Extend(1)
|
||||
|
||||
// Construct a bunch of commits, each with one more height than the last.
|
||||
chainID := "inquiry-test"
|
||||
consHash := []byte("params")
|
||||
resHash := []byte("results")
|
||||
count := 50
|
||||
fcz := make([]FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
vals := keys.ToValidators(vote, 0)
|
||||
nextVals := nkeys.ToValidators(vote, 0)
|
||||
h := int64(1 + i)
|
||||
appHash := []byte(fmt.Sprintf("h=%d", h))
|
||||
fcz[i] = keys.GenFullCommit(
|
||||
chainID, h, nil,
|
||||
vals, nextVals,
|
||||
appHash, consHash, resHash, 0, len(keys))
|
||||
// Extend the keys by 1 each time.
|
||||
keys = nkeys
|
||||
nkeys = nkeys.Extend(1)
|
||||
}
|
||||
|
||||
// Initialize a Verifier with the initial state.
|
||||
err := trust.SaveFullCommit(fcz[0])
|
||||
require.Nil(err)
|
||||
cert := NewDynamicVerifier(chainID, trust, source)
|
||||
cert.SetLogger(log.TestingLogger())
|
||||
|
||||
// This should fail validation:
|
||||
sh := fcz[count-1].SignedHeader
|
||||
err = cert.Certify(sh)
|
||||
require.NotNil(err)
|
||||
|
||||
// Adding a few commits in the middle should be insufficient.
|
||||
for i := 10; i < 13; i++ {
|
||||
err := source.SaveFullCommit(fcz[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
err = cert.Certify(sh)
|
||||
assert.NotNil(err)
|
||||
|
||||
// With more info, we succeed.
|
||||
for i := 0; i < count; i++ {
|
||||
err := source.SaveFullCommit(fcz[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
err = cert.Certify(sh)
|
||||
assert.Nil(err, "%+v", err)
|
||||
}
|
||||
|
||||
func TestInquirerVerifyHistorical(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
trust := NewDBProvider("trust", dbm.NewMemDB())
|
||||
source := NewDBProvider("source", dbm.NewMemDB())
|
||||
|
||||
// Set up the validators to generate test blocks.
|
||||
var vote int64 = 10
|
||||
keys := genPrivKeys(5)
|
||||
nkeys := keys.Extend(1)
|
||||
|
||||
// Construct a bunch of commits, each with one more height than the last.
|
||||
chainID := "inquiry-test"
|
||||
count := 10
|
||||
consHash := []byte("special-params")
|
||||
fcz := make([]FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
vals := keys.ToValidators(vote, 0)
|
||||
nextVals := nkeys.ToValidators(vote, 0)
|
||||
h := int64(1 + i)
|
||||
appHash := []byte(fmt.Sprintf("h=%d", h))
|
||||
resHash := []byte(fmt.Sprintf("res=%d", h))
|
||||
fcz[i] = keys.GenFullCommit(
|
||||
chainID, h, nil,
|
||||
vals, nextVals,
|
||||
appHash, consHash, resHash, 0, len(keys))
|
||||
// Extend the keys by 1 each time.
|
||||
keys = nkeys
|
||||
nkeys = nkeys.Extend(1)
|
||||
}
|
||||
|
||||
// Initialize a Verifier with the initial state.
|
||||
err := trust.SaveFullCommit(fcz[0])
|
||||
require.Nil(err)
|
||||
cert := NewDynamicVerifier(chainID, trust, source)
|
||||
cert.SetLogger(log.TestingLogger())
|
||||
|
||||
// Store a few full commits as trust.
|
||||
for _, i := range []int{2, 5} {
|
||||
trust.SaveFullCommit(fcz[i])
|
||||
}
|
||||
|
||||
// See if we can jump forward using trusted full commits.
|
||||
// Souce doesn't have fcz[9] so cert.LastTrustedHeight wont' change.
|
||||
err = source.SaveFullCommit(fcz[7])
|
||||
require.Nil(err, "%+v", err)
|
||||
sh := fcz[8].SignedHeader
|
||||
err = cert.Certify(sh)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(fcz[7].Height(), cert.LastTrustedHeight())
|
||||
fc_, err := trust.LatestFullCommit(chainID, fcz[8].Height(), fcz[8].Height())
|
||||
require.NotNil(err, "%+v", err)
|
||||
assert.Equal(fc_, (FullCommit{}))
|
||||
|
||||
// With fcz[9] Certify will update last trusted height.
|
||||
err = source.SaveFullCommit(fcz[9])
|
||||
require.Nil(err, "%+v", err)
|
||||
sh = fcz[8].SignedHeader
|
||||
err = cert.Certify(sh)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(fcz[8].Height(), cert.LastTrustedHeight())
|
||||
fc_, err = trust.LatestFullCommit(chainID, fcz[8].Height(), fcz[8].Height())
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(fc_.Height(), fcz[8].Height())
|
||||
|
||||
// Add access to all full commits via untrusted source.
|
||||
for i := 0; i < count; i++ {
|
||||
err := source.SaveFullCommit(fcz[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
|
||||
// Try to check an unknown seed in the past.
|
||||
sh = fcz[3].SignedHeader
|
||||
err = cert.Certify(sh)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(fcz[8].Height(), cert.LastTrustedHeight())
|
||||
|
||||
// Jump all the way forward again.
|
||||
sh = fcz[count-1].SignedHeader
|
||||
err = cert.Certify(sh)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(fcz[9].Height(), cert.LastTrustedHeight())
|
||||
}
|
|
@ -3,90 +3,110 @@ package errors
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
var (
|
||||
errValidatorsChanged = fmt.Errorf("Validators differ between header and certifier")
|
||||
errCommitNotFound = fmt.Errorf("Commit not found by provider")
|
||||
errTooMuchChange = fmt.Errorf("Validators change too much to safely update")
|
||||
errPastTime = fmt.Errorf("Update older than certifier height")
|
||||
errNoPathFound = fmt.Errorf("Cannot find a path of validators")
|
||||
)
|
||||
//----------------------------------------
|
||||
// Error types
|
||||
|
||||
// IsCommitNotFoundErr checks whether an error is due to missing data
|
||||
func IsCommitNotFoundErr(err error) bool {
|
||||
return err != nil && (errors.Cause(err) == errCommitNotFound)
|
||||
type errCommitNotFound struct{}
|
||||
|
||||
func (e errCommitNotFound) Error() string {
|
||||
return "Commit not found by provider"
|
||||
}
|
||||
|
||||
type errUnexpectedValidators struct {
|
||||
got []byte
|
||||
want []byte
|
||||
}
|
||||
|
||||
func (e errUnexpectedValidators) Error() string {
|
||||
return fmt.Sprintf("Validator set is different. Got %X want %X",
|
||||
e.got, e.want)
|
||||
}
|
||||
|
||||
type errTooMuchChange struct{}
|
||||
|
||||
func (e errTooMuchChange) Error() string {
|
||||
return "Insufficient signatures to validate due to valset changes"
|
||||
}
|
||||
|
||||
type errUnknownValidators struct {
|
||||
chainID string
|
||||
height int64
|
||||
}
|
||||
|
||||
func (e errUnknownValidators) Error() string {
|
||||
return fmt.Sprintf("Validators are unknown or missing for chain %s and height %d",
|
||||
e.chainID, e.height)
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
// Methods for above error types
|
||||
|
||||
//-----------------
|
||||
// ErrCommitNotFound
|
||||
|
||||
// ErrCommitNotFound indicates that a the requested commit was not found.
|
||||
func ErrCommitNotFound() error {
|
||||
return errors.WithStack(errCommitNotFound)
|
||||
return cmn.ErrorWrap(errCommitNotFound{}, "")
|
||||
}
|
||||
|
||||
// IsValidatorsChangedErr checks whether an error is due
|
||||
// to a differing validator set.
|
||||
func IsValidatorsChangedErr(err error) bool {
|
||||
return err != nil && (errors.Cause(err) == errValidatorsChanged)
|
||||
func IsErrCommitNotFound(err error) bool {
|
||||
if err_, ok := err.(cmn.Error); ok {
|
||||
_, ok := err_.Data().(errCommitNotFound)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrValidatorsChanged indicates that the validator set was changed between two commits.
|
||||
func ErrValidatorsChanged() error {
|
||||
return errors.WithStack(errValidatorsChanged)
|
||||
//-----------------
|
||||
// ErrUnexpectedValidators
|
||||
|
||||
// ErrUnexpectedValidators indicates a validator set mismatch.
|
||||
func ErrUnexpectedValidators(got, want []byte) error {
|
||||
return cmn.ErrorWrap(errUnexpectedValidators{
|
||||
got: got,
|
||||
want: want,
|
||||
}, "")
|
||||
}
|
||||
|
||||
// IsTooMuchChangeErr checks whether an error is due to too much change
|
||||
// between these validators sets.
|
||||
func IsTooMuchChangeErr(err error) bool {
|
||||
return err != nil && (errors.Cause(err) == errTooMuchChange)
|
||||
func IsErrUnexpectedValidators(err error) bool {
|
||||
if err_, ok := err.(cmn.Error); ok {
|
||||
_, ok := err_.Data().(errUnexpectedValidators)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//-----------------
|
||||
// ErrTooMuchChange
|
||||
|
||||
// ErrTooMuchChange indicates that the underlying validator set was changed by >1/3.
|
||||
func ErrTooMuchChange() error {
|
||||
return errors.WithStack(errTooMuchChange)
|
||||
return cmn.ErrorWrap(errTooMuchChange{}, "")
|
||||
}
|
||||
|
||||
// IsPastTimeErr ...
|
||||
func IsPastTimeErr(err error) bool {
|
||||
return err != nil && (errors.Cause(err) == errPastTime)
|
||||
}
|
||||
|
||||
// ErrPastTime ...
|
||||
func ErrPastTime() error {
|
||||
return errors.WithStack(errPastTime)
|
||||
}
|
||||
|
||||
// IsNoPathFoundErr checks whether an error is due to no path of
|
||||
// validators in provider from where we are to where we want to be
|
||||
func IsNoPathFoundErr(err error) bool {
|
||||
return err != nil && (errors.Cause(err) == errNoPathFound)
|
||||
}
|
||||
|
||||
// ErrNoPathFound ...
|
||||
func ErrNoPathFound() error {
|
||||
return errors.WithStack(errNoPathFound)
|
||||
}
|
||||
|
||||
//--------------------------------------------
|
||||
|
||||
type errHeightMismatch struct {
|
||||
h1, h2 int64
|
||||
}
|
||||
|
||||
func (e errHeightMismatch) Error() string {
|
||||
return fmt.Sprintf("Blocks don't match - %d vs %d", e.h1, e.h2)
|
||||
}
|
||||
|
||||
// IsHeightMismatchErr checks whether an error is due to data from different blocks
|
||||
func IsHeightMismatchErr(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
func IsErrTooMuchChange(err error) bool {
|
||||
if err_, ok := err.(cmn.Error); ok {
|
||||
_, ok := err_.Data().(errTooMuchChange)
|
||||
return ok
|
||||
}
|
||||
_, ok := errors.Cause(err).(errHeightMismatch)
|
||||
return ok
|
||||
return false
|
||||
}
|
||||
|
||||
// ErrHeightMismatch returns an mismatch error with stack-trace
|
||||
func ErrHeightMismatch(h1, h2 int64) error {
|
||||
return errors.WithStack(errHeightMismatch{h1, h2})
|
||||
//-----------------
|
||||
// ErrUnknownValidators
|
||||
|
||||
// ErrUnknownValidators indicates that some validator set was missing or unknown.
|
||||
func ErrUnknownValidators(chainID string, height int64) error {
|
||||
return cmn.ErrorWrap(errUnknownValidators{chainID, height}, "")
|
||||
}
|
||||
|
||||
func IsErrUnknownValidators(err error) bool {
|
||||
if err_, ok := err.(cmn.Error); ok {
|
||||
_, ok := err_.Data().(errUnknownValidators)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrorHeight(t *testing.T) {
|
||||
e1 := ErrHeightMismatch(2, 3)
|
||||
e1.Error()
|
||||
assert.True(t, IsHeightMismatchErr(e1))
|
||||
|
||||
e2 := errors.New("foobar")
|
||||
assert.False(t, IsHeightMismatchErr(e2))
|
||||
assert.False(t, IsHeightMismatchErr(nil))
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// MaxFullCommitSize is the maximum number of bytes we will
|
||||
// read in for a full commit to avoid excessive allocations
|
||||
// in the deserializer
|
||||
MaxFullCommitSize = 1024 * 1024
|
||||
)
|
||||
|
||||
// SaveFullCommit exports the seed in binary / go-amino style
|
||||
func SaveFullCommit(fc lite.FullCommit, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = cdc.MarshalBinaryWriter(f, fc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveFullCommitJSON exports the seed in a json format
|
||||
func SaveFullCommitJSON(fc lite.FullCommit, path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
bz, err := cdc.MarshalJSON(fc)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
_, err = f.Write(bz)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFullCommit loads the full commit from the file system.
|
||||
func LoadFullCommit(path string) (lite.FullCommit, error) {
|
||||
var fc lite.FullCommit
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fc, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
return fc, errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = cdc.UnmarshalBinaryReader(f, &fc, 0)
|
||||
if err != nil {
|
||||
return fc, errors.WithStack(err)
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
// LoadFullCommitJSON loads the commit from the file system in JSON format.
|
||||
func LoadFullCommitJSON(path string) (lite.FullCommit, error) {
|
||||
var fc lite.FullCommit
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fc, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
return fc, errors.WithStack(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bz, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return fc, errors.WithStack(err)
|
||||
}
|
||||
err = cdc.UnmarshalJSON(bz, &fc)
|
||||
if err != nil {
|
||||
return fc, errors.WithStack(err)
|
||||
}
|
||||
return fc, nil
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
)
|
||||
|
||||
func tmpFile() string {
|
||||
suffix := cmn.RandStr(16)
|
||||
return filepath.Join(os.TempDir(), "fc-test-"+suffix)
|
||||
}
|
||||
|
||||
func TestSerializeFullCommits(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
// some constants
|
||||
appHash := []byte("some crazy thing")
|
||||
chainID := "ser-ial"
|
||||
h := int64(25)
|
||||
|
||||
// build a fc
|
||||
keys := lite.GenValKeys(5)
|
||||
vals := keys.ToValidators(10, 0)
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
|
||||
require.Equal(h, fc.Height())
|
||||
require.Equal(vals.Hash(), fc.ValidatorsHash())
|
||||
|
||||
// try read/write with json
|
||||
jfile := tmpFile()
|
||||
defer os.Remove(jfile)
|
||||
jseed, err := LoadFullCommitJSON(jfile)
|
||||
assert.NotNil(err)
|
||||
err = SaveFullCommitJSON(fc, jfile)
|
||||
require.Nil(err)
|
||||
jseed, err = LoadFullCommitJSON(jfile)
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Equal(h, jseed.Height())
|
||||
assert.Equal(vals.Hash(), jseed.ValidatorsHash())
|
||||
|
||||
// try read/write with binary
|
||||
bfile := tmpFile()
|
||||
defer os.Remove(bfile)
|
||||
bseed, err := LoadFullCommit(bfile)
|
||||
assert.NotNil(err)
|
||||
err = SaveFullCommit(fc, bfile)
|
||||
require.Nil(err)
|
||||
bseed, err = LoadFullCommit(bfile)
|
||||
assert.Nil(err, "%+v", err)
|
||||
assert.Equal(h, bseed.Height())
|
||||
assert.Equal(vals.Hash(), bseed.ValidatorsHash())
|
||||
|
||||
// make sure they don't read the other format (different)
|
||||
_, err = LoadFullCommit(jfile)
|
||||
assert.NotNil(err)
|
||||
_, err = LoadFullCommitJSON(bfile)
|
||||
assert.NotNil(err)
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
Package files defines a Provider that stores all data in the filesystem
|
||||
|
||||
We assume the same validator hash may be reused by many different
|
||||
headers/Commits, and thus store it separately. This leaves us
|
||||
with three issues:
|
||||
|
||||
1. Given a validator hash, retrieve the validator set if previously stored
|
||||
2. Given a block height, find the Commit with the highest height <= h
|
||||
3. Given a FullCommit, store it quickly to satisfy 1 and 2
|
||||
|
||||
Note that we do not worry about caching, as that can be achieved by
|
||||
pairing this with a MemStoreProvider and CacheProvider from certifiers
|
||||
*/
|
||||
package files
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
// nolint
|
||||
const (
|
||||
Ext = ".tsd"
|
||||
ValDir = "validators"
|
||||
CheckDir = "checkpoints"
|
||||
dirPerm = os.FileMode(0755)
|
||||
//filePerm = os.FileMode(0644)
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
valDir string
|
||||
checkDir string
|
||||
}
|
||||
|
||||
// NewProvider creates the parent dir and subdirs
|
||||
// for validators and checkpoints as needed
|
||||
func NewProvider(dir string) lite.Provider {
|
||||
valDir := filepath.Join(dir, ValDir)
|
||||
checkDir := filepath.Join(dir, CheckDir)
|
||||
for _, d := range []string{valDir, checkDir} {
|
||||
err := os.MkdirAll(d, dirPerm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return &provider{valDir: valDir, checkDir: checkDir}
|
||||
}
|
||||
|
||||
func (p *provider) encodeHash(hash []byte) string {
|
||||
return hex.EncodeToString(hash) + Ext
|
||||
}
|
||||
|
||||
func (p *provider) encodeHeight(h int64) string {
|
||||
// pad up to 10^12 for height...
|
||||
return fmt.Sprintf("%012d%s", h, Ext)
|
||||
}
|
||||
|
||||
// StoreCommit saves a full commit after it has been verified.
|
||||
func (p *provider) StoreCommit(fc lite.FullCommit) error {
|
||||
// make sure the fc is self-consistent before saving
|
||||
err := fc.ValidateBasic(fc.Commit.Header.ChainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
filepath.Join(p.checkDir, p.encodeHeight(fc.Height())),
|
||||
filepath.Join(p.valDir, p.encodeHash(fc.Header.ValidatorsHash)),
|
||||
}
|
||||
for _, path := range paths {
|
||||
err := SaveFullCommit(fc, path)
|
||||
// unknown error in creating or writing immediately breaks
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByHeight returns the closest commit with height <= h.
|
||||
func (p *provider) GetByHeight(h int64) (lite.FullCommit, error) {
|
||||
// first we look for exact match, then search...
|
||||
path := filepath.Join(p.checkDir, p.encodeHeight(h))
|
||||
fc, err := LoadFullCommit(path)
|
||||
if liteErr.IsCommitNotFoundErr(err) {
|
||||
path, err = p.searchForHeight(h)
|
||||
if err == nil {
|
||||
fc, err = LoadFullCommit(path)
|
||||
}
|
||||
}
|
||||
return fc, err
|
||||
}
|
||||
|
||||
// LatestCommit returns the newest commit stored.
|
||||
func (p *provider) LatestCommit() (fc lite.FullCommit, err error) {
|
||||
// Note to future: please update by 2077 to avoid rollover
|
||||
return p.GetByHeight(math.MaxInt32 - 1)
|
||||
}
|
||||
|
||||
// search for height, looks for a file with highest height < h
|
||||
// return certifiers.ErrCommitNotFound() if not there...
|
||||
func (p *provider) searchForHeight(h int64) (string, error) {
|
||||
d, err := os.Open(p.checkDir)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
files, err := d.Readdirnames(0)
|
||||
|
||||
d.Close()
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
desired := p.encodeHeight(h)
|
||||
sort.Strings(files)
|
||||
i := sort.SearchStrings(files, desired)
|
||||
if i == 0 {
|
||||
return "", liteErr.ErrCommitNotFound()
|
||||
}
|
||||
found := files[i-1]
|
||||
path := filepath.Join(p.checkDir, found)
|
||||
return path, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// GetByHash returns a commit exactly matching this validator hash.
|
||||
func (p *provider) GetByHash(hash []byte) (lite.FullCommit, error) {
|
||||
path := filepath.Join(p.valDir, p.encodeHash(hash))
|
||||
return LoadFullCommit(path)
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
package files_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/lite/files"
|
||||
)
|
||||
|
||||
func checkEqual(stored, loaded lite.FullCommit, chainID string) error {
|
||||
err := loaded.ValidateBasic(chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(stored.ValidatorsHash(), loaded.ValidatorsHash()) {
|
||||
return errors.New("Different block hashes")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFileProvider(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
|
||||
dir, err := ioutil.TempDir("", "fileprovider-test")
|
||||
assert.Nil(err)
|
||||
defer os.RemoveAll(dir)
|
||||
p := files.NewProvider(dir)
|
||||
|
||||
chainID := "test-files"
|
||||
appHash := []byte("some-data")
|
||||
keys := lite.GenValKeys(5)
|
||||
count := 10
|
||||
|
||||
// make a bunch of seeds...
|
||||
seeds := make([]lite.FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
// two seeds for each validator, to check how we handle dups
|
||||
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
|
||||
vals := keys.ToValidators(10, int64(count/2))
|
||||
h := int64(20 + 10*i)
|
||||
check := keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
seeds[i] = lite.NewFullCommit(check, vals)
|
||||
}
|
||||
|
||||
// check provider is empty
|
||||
seed, err := p.GetByHeight(20)
|
||||
require.NotNil(err)
|
||||
assert.True(liteErr.IsCommitNotFoundErr(err))
|
||||
|
||||
seed, err = p.GetByHash(seeds[3].ValidatorsHash())
|
||||
require.NotNil(err)
|
||||
assert.True(liteErr.IsCommitNotFoundErr(err))
|
||||
|
||||
// now add them all to the provider
|
||||
for _, s := range seeds {
|
||||
err = p.StoreCommit(s)
|
||||
require.Nil(err)
|
||||
// and make sure we can get it back
|
||||
s2, err := p.GetByHash(s.ValidatorsHash())
|
||||
assert.Nil(err)
|
||||
err = checkEqual(s, s2, chainID)
|
||||
assert.Nil(err)
|
||||
// by height as well
|
||||
s2, err = p.GetByHeight(s.Height())
|
||||
err = checkEqual(s, s2, chainID)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
// make sure we get the last hash if we overstep
|
||||
seed, err = p.GetByHeight(5000)
|
||||
if assert.Nil(err, "%+v", err) {
|
||||
assert.Equal(seeds[count-1].Height(), seed.Height())
|
||||
err = checkEqual(seeds[count-1], seed, chainID)
|
||||
assert.Nil(err)
|
||||
}
|
||||
|
||||
// and middle ones as well
|
||||
seed, err = p.GetByHeight(47)
|
||||
if assert.Nil(err, "%+v", err) {
|
||||
// we only step by 10, so 40 must be the one below this
|
||||
assert.EqualValues(40, seed.Height())
|
||||
}
|
||||
|
||||
// and proper error for too low
|
||||
_, err = p.GetByHeight(5)
|
||||
assert.NotNil(err)
|
||||
assert.True(liteErr.IsCommitNotFoundErr(err))
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
var cdc = amino.NewCodec()
|
||||
|
||||
func init() {
|
||||
types.RegisterBlockAmino(cdc)
|
||||
}
|
115
lite/helpers.go
115
lite/helpers.go
|
@ -10,20 +10,18 @@ import (
|
|||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// ValKeys is a helper for testing.
|
||||
// privKeys is a helper type for testing.
|
||||
//
|
||||
// It lets us simulate signing with many keys, either ed25519 or secp256k1.
|
||||
// The main use case is to create a set, and call GenCommit
|
||||
// to get properly signed header for testing.
|
||||
// It lets us simulate signing with many keys. The main use case is to create
|
||||
// a set, and call GenSignedHeader to get properly signed header for testing.
|
||||
//
|
||||
// You can set different weights of validators each time you call
|
||||
// ToValidators, and can optionally extend the validator set later
|
||||
// with Extend or ExtendSecp
|
||||
type ValKeys []crypto.PrivKey
|
||||
// You can set different weights of validators each time you call ToValidators,
|
||||
// and can optionally extend the validator set later with Extend.
|
||||
type privKeys []crypto.PrivKey
|
||||
|
||||
// GenValKeys produces an array of private keys to generate commits.
|
||||
func GenValKeys(n int) ValKeys {
|
||||
res := make(ValKeys, n)
|
||||
// genPrivKeys produces an array of private keys to generate commits.
|
||||
func genPrivKeys(n int) privKeys {
|
||||
res := make(privKeys, n)
|
||||
for i := range res {
|
||||
res[i] = ed25519.GenPrivKey()
|
||||
}
|
||||
|
@ -31,22 +29,22 @@ func GenValKeys(n int) ValKeys {
|
|||
}
|
||||
|
||||
// Change replaces the key at index i.
|
||||
func (v ValKeys) Change(i int) ValKeys {
|
||||
res := make(ValKeys, len(v))
|
||||
copy(res, v)
|
||||
func (pkz privKeys) Change(i int) privKeys {
|
||||
res := make(privKeys, len(pkz))
|
||||
copy(res, pkz)
|
||||
res[i] = ed25519.GenPrivKey()
|
||||
return res
|
||||
}
|
||||
|
||||
// Extend adds n more keys (to remove, just take a slice).
|
||||
func (v ValKeys) Extend(n int) ValKeys {
|
||||
extra := GenValKeys(n)
|
||||
return append(v, extra...)
|
||||
func (pkz privKeys) Extend(n int) privKeys {
|
||||
extra := genPrivKeys(n)
|
||||
return append(pkz, extra...)
|
||||
}
|
||||
|
||||
// GenSecpValKeys produces an array of secp256k1 private keys to generate commits.
|
||||
func GenSecpValKeys(n int) ValKeys {
|
||||
res := make(ValKeys, n)
|
||||
// GenSecpPrivKeys produces an array of secp256k1 private keys to generate commits.
|
||||
func GenSecpPrivKeys(n int) privKeys {
|
||||
res := make(privKeys, n)
|
||||
for i := range res {
|
||||
res[i] = secp256k1.GenPrivKey()
|
||||
}
|
||||
|
@ -54,33 +52,33 @@ func GenSecpValKeys(n int) ValKeys {
|
|||
}
|
||||
|
||||
// ExtendSecp adds n more secp256k1 keys (to remove, just take a slice).
|
||||
func (v ValKeys) ExtendSecp(n int) ValKeys {
|
||||
extra := GenSecpValKeys(n)
|
||||
return append(v, extra...)
|
||||
func (pkz privKeys) ExtendSecp(n int) privKeys {
|
||||
extra := GenSecpPrivKeys(n)
|
||||
return append(pkz, extra...)
|
||||
}
|
||||
|
||||
// ToValidators produces a list of validators from the set of keys
|
||||
// ToValidators produces a valset from the set of keys.
|
||||
// The first key has weight `init` and it increases by `inc` every step
|
||||
// so we can have all the same weight, or a simple linear distribution
|
||||
// (should be enough for testing).
|
||||
func (v ValKeys) ToValidators(init, inc int64) *types.ValidatorSet {
|
||||
res := make([]*types.Validator, len(v))
|
||||
for i, k := range v {
|
||||
func (pkz privKeys) ToValidators(init, inc int64) *types.ValidatorSet {
|
||||
res := make([]*types.Validator, len(pkz))
|
||||
for i, k := range pkz {
|
||||
res[i] = types.NewValidator(k.PubKey(), init+int64(i)*inc)
|
||||
}
|
||||
return types.NewValidatorSet(res)
|
||||
}
|
||||
|
||||
// signHeader properly signs the header with all keys from first to last exclusive.
|
||||
func (v ValKeys) signHeader(header *types.Header, first, last int) *types.Commit {
|
||||
votes := make([]*types.Vote, len(v))
|
||||
func (pkz privKeys) signHeader(header *types.Header, first, last int) *types.Commit {
|
||||
votes := make([]*types.Vote, len(pkz))
|
||||
|
||||
// we need this list to keep the ordering...
|
||||
vset := v.ToValidators(1, 0)
|
||||
// We need this list to keep the ordering.
|
||||
vset := pkz.ToValidators(1, 0)
|
||||
|
||||
// fill in the votes we want
|
||||
for i := first; i < last && i < len(v); i++ {
|
||||
vote := makeVote(header, vset, v[i])
|
||||
// Fill in the votes we want.
|
||||
for i := first; i < last && i < len(pkz); i++ {
|
||||
vote := makeVote(header, vset, pkz[i])
|
||||
votes[vote.ValidatorIndex] = vote
|
||||
}
|
||||
|
||||
|
@ -91,15 +89,15 @@ func (v ValKeys) signHeader(header *types.Header, first, last int) *types.Commit
|
|||
return res
|
||||
}
|
||||
|
||||
func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey) *types.Vote {
|
||||
func makeVote(header *types.Header, valset *types.ValidatorSet, key crypto.PrivKey) *types.Vote {
|
||||
addr := key.PubKey().Address()
|
||||
idx, _ := vals.GetByAddress(addr)
|
||||
idx, _ := valset.GetByAddress(addr)
|
||||
vote := &types.Vote{
|
||||
ValidatorAddress: addr,
|
||||
ValidatorIndex: idx,
|
||||
Height: header.Height,
|
||||
Round: 1,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Timestamp: time.Now().Round(0).UTC(),
|
||||
Type: types.VoteTypePrecommit,
|
||||
BlockID: types.BlockID{Hash: header.Hash()},
|
||||
}
|
||||
|
@ -115,47 +113,46 @@ func makeVote(header *types.Header, vals *types.ValidatorSet, key crypto.PrivKey
|
|||
return vote
|
||||
}
|
||||
|
||||
// Silences warning that vals can also be merkle.Hashable
|
||||
// nolint: interfacer
|
||||
func genHeader(chainID string, height int64, txs types.Txs,
|
||||
vals *types.ValidatorSet, appHash, consHash, resHash []byte) *types.Header {
|
||||
valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte) *types.Header {
|
||||
|
||||
return &types.Header{
|
||||
ChainID: chainID,
|
||||
Height: height,
|
||||
Time: time.Now(),
|
||||
Time: time.Now().Round(0).UTC(),
|
||||
NumTxs: int64(len(txs)),
|
||||
TotalTxs: int64(len(txs)),
|
||||
// LastBlockID
|
||||
// LastCommitHash
|
||||
ValidatorsHash: vals.Hash(),
|
||||
DataHash: txs.Hash(),
|
||||
AppHash: appHash,
|
||||
ConsensusHash: consHash,
|
||||
LastResultsHash: resHash,
|
||||
ValidatorsHash: valset.Hash(),
|
||||
NextValidatorsHash: nextValset.Hash(),
|
||||
DataHash: txs.Hash(),
|
||||
AppHash: appHash,
|
||||
ConsensusHash: consHash,
|
||||
LastResultsHash: resHash,
|
||||
}
|
||||
}
|
||||
|
||||
// GenCommit calls genHeader and signHeader and combines them into a Commit.
|
||||
func (v ValKeys) GenCommit(chainID string, height int64, txs types.Txs,
|
||||
vals *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) Commit {
|
||||
// GenSignedHeader calls genHeader and signHeader and combines them into a SignedHeader.
|
||||
func (pkz privKeys) GenSignedHeader(chainID string, height int64, txs types.Txs,
|
||||
valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) types.SignedHeader {
|
||||
|
||||
header := genHeader(chainID, height, txs, vals, appHash, consHash, resHash)
|
||||
check := Commit{
|
||||
header := genHeader(chainID, height, txs, valset, nextValset, appHash, consHash, resHash)
|
||||
check := types.SignedHeader{
|
||||
Header: header,
|
||||
Commit: v.signHeader(header, first, last),
|
||||
Commit: pkz.signHeader(header, first, last),
|
||||
}
|
||||
return check
|
||||
}
|
||||
|
||||
// GenFullCommit calls genHeader and signHeader and combines them into a Commit.
|
||||
func (v ValKeys) GenFullCommit(chainID string, height int64, txs types.Txs,
|
||||
vals *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) FullCommit {
|
||||
// GenFullCommit calls genHeader and signHeader and combines them into a FullCommit.
|
||||
func (pkz privKeys) GenFullCommit(chainID string, height int64, txs types.Txs,
|
||||
valset, nextValset *types.ValidatorSet, appHash, consHash, resHash []byte, first, last int) FullCommit {
|
||||
|
||||
header := genHeader(chainID, height, txs, vals, appHash, consHash, resHash)
|
||||
commit := Commit{
|
||||
header := genHeader(chainID, height, txs, valset, nextValset, appHash, consHash, resHash)
|
||||
commit := types.SignedHeader{
|
||||
Header: header,
|
||||
Commit: v.signHeader(header, first, last),
|
||||
Commit: pkz.signHeader(header, first, last),
|
||||
}
|
||||
return NewFullCommit(commit, vals)
|
||||
return NewFullCommit(commit, valset, nextValset)
|
||||
}
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
var _ Certifier = (*InquiringCertifier)(nil)
|
||||
|
||||
// InquiringCertifier wraps a dynamic certifier and implements an auto-update strategy. If a call
|
||||
// to Certify fails due to a change it validator set, InquiringCertifier will try and find a
|
||||
// previous FullCommit which it can use to safely update the validator set. It uses a source
|
||||
// provider to obtain the needed FullCommits. It stores properly validated data on the local system.
|
||||
type InquiringCertifier struct {
|
||||
cert *DynamicCertifier
|
||||
// These are only properly validated data, from local system
|
||||
trusted Provider
|
||||
// This is a source of new info, like a node rpc, or other import method
|
||||
Source Provider
|
||||
}
|
||||
|
||||
// NewInquiringCertifier returns a new Inquiring object. It uses the trusted provider to store
|
||||
// validated data and the source provider to obtain missing FullCommits.
|
||||
//
|
||||
// Example: The trusted provider should a CacheProvider, MemProvider or files.Provider. The source
|
||||
// provider should be a client.HTTPProvider.
|
||||
func NewInquiringCertifier(chainID string, fc FullCommit, trusted Provider,
|
||||
source Provider) (*InquiringCertifier, error) {
|
||||
|
||||
// store the data in trusted
|
||||
err := trusted.StoreCommit(fc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &InquiringCertifier{
|
||||
cert: NewDynamicCertifier(chainID, fc.Validators, fc.Height()),
|
||||
trusted: trusted,
|
||||
Source: source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ChainID returns the chain id.
|
||||
// Implements Certifier.
|
||||
func (ic *InquiringCertifier) ChainID() string {
|
||||
return ic.cert.ChainID()
|
||||
}
|
||||
|
||||
// Validators returns the validator set.
|
||||
func (ic *InquiringCertifier) Validators() *types.ValidatorSet {
|
||||
return ic.cert.cert.vSet
|
||||
}
|
||||
|
||||
// LastHeight returns the last height.
|
||||
func (ic *InquiringCertifier) LastHeight() int64 {
|
||||
return ic.cert.lastHeight
|
||||
}
|
||||
|
||||
// Certify makes sure this is checkpoint is valid.
|
||||
//
|
||||
// If the validators have changed since the last know time, it looks
|
||||
// for a path to prove the new validators.
|
||||
//
|
||||
// On success, it will store the checkpoint in the store for later viewing
|
||||
// Implements Certifier.
|
||||
func (ic *InquiringCertifier) Certify(commit Commit) error {
|
||||
err := ic.useClosestTrust(commit.Height())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ic.cert.Certify(commit)
|
||||
if !liteErr.IsValidatorsChangedErr(err) {
|
||||
return err
|
||||
}
|
||||
err = ic.updateToHash(commit.Header.ValidatorsHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ic.cert.Certify(commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// store the new checkpoint
|
||||
return ic.trusted.StoreCommit(NewFullCommit(commit, ic.Validators()))
|
||||
}
|
||||
|
||||
// Update will verify if this is a valid change and update
|
||||
// the certifying validator set if safe to do so.
|
||||
func (ic *InquiringCertifier) Update(fc FullCommit) error {
|
||||
err := ic.useClosestTrust(fc.Height())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = ic.cert.Update(fc)
|
||||
if err == nil {
|
||||
err = ic.trusted.StoreCommit(fc)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ic *InquiringCertifier) useClosestTrust(h int64) error {
|
||||
closest, err := ic.trusted.GetByHeight(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the best seed is not the one we currently use,
|
||||
// let's just reset the dynamic validator
|
||||
if closest.Height() != ic.LastHeight() {
|
||||
ic.cert = NewDynamicCertifier(ic.ChainID(), closest.Validators, closest.Height())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateToHash gets the validator hash we want to update to
|
||||
// if IsTooMuchChangeErr, we try to find a path by binary search over height
|
||||
func (ic *InquiringCertifier) updateToHash(vhash []byte) error {
|
||||
// try to get the match, and update
|
||||
fc, err := ic.Source.GetByHash(vhash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ic.cert.Update(fc)
|
||||
// handle IsTooMuchChangeErr by using divide and conquer
|
||||
if liteErr.IsTooMuchChangeErr(err) {
|
||||
err = ic.updateToHeight(fc.Height())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// updateToHeight will use divide-and-conquer to find a path to h
|
||||
func (ic *InquiringCertifier) updateToHeight(h int64) error {
|
||||
// try to update to this height (with checks)
|
||||
fc, err := ic.Source.GetByHeight(h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start, end := ic.LastHeight(), fc.Height()
|
||||
if end <= start {
|
||||
return liteErr.ErrNoPathFound()
|
||||
}
|
||||
err = ic.Update(fc)
|
||||
|
||||
// we can handle IsTooMuchChangeErr specially
|
||||
if !liteErr.IsTooMuchChangeErr(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// try to update to mid
|
||||
mid := (start + end) / 2
|
||||
err = ic.updateToHeight(mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if we made it to mid, we recurse
|
||||
return ic.updateToHeight(h)
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
// nolint: vetshadow
|
||||
package lite_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
)
|
||||
|
||||
func TestInquirerValidPath(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
trust := lite.NewMemStoreProvider()
|
||||
source := lite.NewMemStoreProvider()
|
||||
|
||||
// set up the validators to generate test blocks
|
||||
var vote int64 = 10
|
||||
keys := lite.GenValKeys(5)
|
||||
|
||||
// construct a bunch of commits, each with one more height than the last
|
||||
chainID := "inquiry-test"
|
||||
consHash := []byte("params")
|
||||
resHash := []byte("results")
|
||||
count := 50
|
||||
commits := make([]lite.FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
// extend the keys by 1 each time
|
||||
keys = keys.Extend(1)
|
||||
vals := keys.ToValidators(vote, 0)
|
||||
h := int64(20 + 10*i)
|
||||
appHash := []byte(fmt.Sprintf("h=%d", h))
|
||||
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0,
|
||||
len(keys))
|
||||
}
|
||||
|
||||
// initialize a certifier with the initial state
|
||||
cert, err := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
|
||||
require.Nil(err)
|
||||
|
||||
// this should fail validation....
|
||||
commit := commits[count-1].Commit
|
||||
err = cert.Certify(commit)
|
||||
require.NotNil(err)
|
||||
|
||||
// adding a few commits in the middle should be insufficient
|
||||
for i := 10; i < 13; i++ {
|
||||
err := source.StoreCommit(commits[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
err = cert.Certify(commit)
|
||||
assert.NotNil(err)
|
||||
|
||||
// with more info, we succeed
|
||||
for i := 0; i < count; i++ {
|
||||
err := source.StoreCommit(commits[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
err = cert.Certify(commit)
|
||||
assert.Nil(err, "%+v", err)
|
||||
}
|
||||
|
||||
func TestInquirerMinimalPath(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
trust := lite.NewMemStoreProvider()
|
||||
source := lite.NewMemStoreProvider()
|
||||
|
||||
// set up the validators to generate test blocks
|
||||
var vote int64 = 10
|
||||
keys := lite.GenValKeys(5)
|
||||
|
||||
// construct a bunch of commits, each with one more height than the last
|
||||
chainID := "minimal-path"
|
||||
consHash := []byte("other-params")
|
||||
count := 12
|
||||
commits := make([]lite.FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
// extend the validators, so we are just below 2/3
|
||||
keys = keys.Extend(len(keys)/2 - 1)
|
||||
vals := keys.ToValidators(vote, 0)
|
||||
h := int64(5 + 10*i)
|
||||
appHash := []byte(fmt.Sprintf("h=%d", h))
|
||||
resHash := []byte(fmt.Sprintf("res=%d", h))
|
||||
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0,
|
||||
len(keys))
|
||||
}
|
||||
|
||||
// initialize a certifier with the initial state
|
||||
cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
|
||||
|
||||
// this should fail validation....
|
||||
commit := commits[count-1].Commit
|
||||
err := cert.Certify(commit)
|
||||
require.NotNil(err)
|
||||
|
||||
// add a few seed in the middle should be insufficient
|
||||
for i := 5; i < 8; i++ {
|
||||
err := source.StoreCommit(commits[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
err = cert.Certify(commit)
|
||||
assert.NotNil(err)
|
||||
|
||||
// with more info, we succeed
|
||||
for i := 0; i < count; i++ {
|
||||
err := source.StoreCommit(commits[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
err = cert.Certify(commit)
|
||||
assert.Nil(err, "%+v", err)
|
||||
}
|
||||
|
||||
func TestInquirerVerifyHistorical(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
trust := lite.NewMemStoreProvider()
|
||||
source := lite.NewMemStoreProvider()
|
||||
|
||||
// set up the validators to generate test blocks
|
||||
var vote int64 = 10
|
||||
keys := lite.GenValKeys(5)
|
||||
|
||||
// construct a bunch of commits, each with one more height than the last
|
||||
chainID := "inquiry-test"
|
||||
count := 10
|
||||
consHash := []byte("special-params")
|
||||
commits := make([]lite.FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
// extend the keys by 1 each time
|
||||
keys = keys.Extend(1)
|
||||
vals := keys.ToValidators(vote, 0)
|
||||
h := int64(20 + 10*i)
|
||||
appHash := []byte(fmt.Sprintf("h=%d", h))
|
||||
resHash := []byte(fmt.Sprintf("res=%d", h))
|
||||
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, consHash, resHash, 0,
|
||||
len(keys))
|
||||
}
|
||||
|
||||
// initialize a certifier with the initial state
|
||||
cert, _ := lite.NewInquiringCertifier(chainID, commits[0], trust, source)
|
||||
|
||||
// store a few commits as trust
|
||||
for _, i := range []int{2, 5} {
|
||||
trust.StoreCommit(commits[i])
|
||||
}
|
||||
|
||||
// let's see if we can jump forward using trusted commits
|
||||
err := source.StoreCommit(commits[7])
|
||||
require.Nil(err, "%+v", err)
|
||||
check := commits[7].Commit
|
||||
err = cert.Certify(check)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(check.Height(), cert.LastHeight())
|
||||
|
||||
// add access to all commits via untrusted source
|
||||
for i := 0; i < count; i++ {
|
||||
err := source.StoreCommit(commits[i])
|
||||
require.Nil(err)
|
||||
}
|
||||
|
||||
// try to check an unknown seed in the past
|
||||
mid := commits[3].Commit
|
||||
err = cert.Certify(mid)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(mid.Height(), cert.LastHeight())
|
||||
|
||||
// and jump all the way forward again
|
||||
end := commits[count-1].Commit
|
||||
err = cert.Certify(end)
|
||||
require.Nil(err, "%+v", err)
|
||||
assert.Equal(end.Height(), cert.LastHeight())
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
type memStoreProvider struct {
|
||||
mtx sync.RWMutex
|
||||
// byHeight is always sorted by Height... need to support range search (nil, h]
|
||||
// btree would be more efficient for larger sets
|
||||
byHeight fullCommits
|
||||
byHash map[string]FullCommit
|
||||
|
||||
sorted bool
|
||||
}
|
||||
|
||||
// fullCommits just exists to allow easy sorting
|
||||
type fullCommits []FullCommit
|
||||
|
||||
func (s fullCommits) Len() int { return len(s) }
|
||||
func (s fullCommits) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s fullCommits) Less(i, j int) bool {
|
||||
return s[i].Height() < s[j].Height()
|
||||
}
|
||||
|
||||
// NewMemStoreProvider returns a new in-memory provider.
|
||||
func NewMemStoreProvider() Provider {
|
||||
return &memStoreProvider{
|
||||
byHeight: fullCommits{},
|
||||
byHash: map[string]FullCommit{},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) encodeHash(hash []byte) string {
|
||||
return hex.EncodeToString(hash)
|
||||
}
|
||||
|
||||
// StoreCommit stores a FullCommit after verifying it.
|
||||
func (m *memStoreProvider) StoreCommit(fc FullCommit) error {
|
||||
// make sure the fc is self-consistent before saving
|
||||
err := fc.ValidateBasic(fc.Commit.Header.ChainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// store the valid fc
|
||||
key := m.encodeHash(fc.ValidatorsHash())
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.byHash[key] = fc
|
||||
m.byHeight = append(m.byHeight, fc)
|
||||
m.sorted = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByHeight returns the FullCommit for height h or an error if the commit is not found.
|
||||
func (m *memStoreProvider) GetByHeight(h int64) (FullCommit, error) {
|
||||
// By heuristics, GetByHeight with linearsearch is fast enough
|
||||
// for about 50 keys but after that, it needs binary search.
|
||||
// See https://github.com/tendermint/tendermint/pull/1043#issue-285188242
|
||||
m.mtx.RLock()
|
||||
n := len(m.byHeight)
|
||||
m.mtx.RUnlock()
|
||||
|
||||
if n <= 50 {
|
||||
return m.getByHeightLinearSearch(h)
|
||||
}
|
||||
return m.getByHeightBinarySearch(h)
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) sortByHeightIfNecessaryLocked() {
|
||||
if !m.sorted {
|
||||
sort.Sort(m.byHeight)
|
||||
m.sorted = true
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) getByHeightLinearSearch(h int64) (FullCommit, error) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.sortByHeightIfNecessaryLocked()
|
||||
// search from highest to lowest
|
||||
for i := len(m.byHeight) - 1; i >= 0; i-- {
|
||||
if fc := m.byHeight[i]; fc.Height() <= h {
|
||||
return fc, nil
|
||||
}
|
||||
}
|
||||
return FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
func (m *memStoreProvider) getByHeightBinarySearch(h int64) (FullCommit, error) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.sortByHeightIfNecessaryLocked()
|
||||
low, high := 0, len(m.byHeight)-1
|
||||
var mid int
|
||||
var hmid int64
|
||||
var midFC FullCommit
|
||||
// Our goal is to either find:
|
||||
// * item ByHeight with the query
|
||||
// * greatest height with a height <= query
|
||||
for low <= high {
|
||||
mid = int(uint(low+high) >> 1) // Avoid an overflow
|
||||
midFC = m.byHeight[mid]
|
||||
hmid = midFC.Height()
|
||||
switch {
|
||||
case hmid == h:
|
||||
return midFC, nil
|
||||
case hmid < h:
|
||||
low = mid + 1
|
||||
case hmid > h:
|
||||
high = mid - 1
|
||||
}
|
||||
}
|
||||
|
||||
if high >= 0 {
|
||||
if highFC := m.byHeight[high]; highFC.Height() < h {
|
||||
return highFC, nil
|
||||
}
|
||||
}
|
||||
return FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
|
||||
// GetByHash returns the FullCommit for the hash or an error if the commit is not found.
|
||||
func (m *memStoreProvider) GetByHash(hash []byte) (FullCommit, error) {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
|
||||
fc, ok := m.byHash[m.encodeHash(hash)]
|
||||
if !ok {
|
||||
return fc, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
// LatestCommit returns the latest FullCommit or an error if no commits exist.
|
||||
func (m *memStoreProvider) LatestCommit() (FullCommit, error) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
l := len(m.byHeight)
|
||||
if l == 0 {
|
||||
return FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
m.sortByHeightIfNecessaryLocked()
|
||||
return m.byHeight[l-1], nil
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// multiProvider allows you to place one or more caches in front of a source
|
||||
// Provider. It runs through them in order until a match is found.
|
||||
type multiProvider struct {
|
||||
logger log.Logger
|
||||
providers []PersistentProvider
|
||||
}
|
||||
|
||||
// NewMultiProvider returns a new provider which wraps multiple other providers.
|
||||
func NewMultiProvider(providers ...PersistentProvider) *multiProvider {
|
||||
return &multiProvider{
|
||||
logger: log.NewNopLogger(),
|
||||
providers: providers,
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogger sets logger on self and all subproviders.
|
||||
func (mc *multiProvider) SetLogger(logger log.Logger) {
|
||||
mc.logger = logger
|
||||
for _, p := range mc.providers {
|
||||
p.SetLogger(logger)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveFullCommit saves on all providers, and aborts on the first error.
|
||||
func (mc *multiProvider) SaveFullCommit(fc FullCommit) (err error) {
|
||||
for _, p := range mc.providers {
|
||||
err = p.SaveFullCommit(fc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LatestFullCommit loads the latest from all providers and provides
|
||||
// the latest FullCommit that satisfies the conditions.
|
||||
// Returns the first error encountered.
|
||||
func (mc *multiProvider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (fc FullCommit, err error) {
|
||||
for _, p := range mc.providers {
|
||||
var fc_ FullCommit
|
||||
fc_, err = p.LatestFullCommit(chainID, minHeight, maxHeight)
|
||||
if lerr.IsErrCommitNotFound(err) {
|
||||
err = nil
|
||||
continue
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
if fc == (FullCommit{}) {
|
||||
fc = fc_
|
||||
} else if fc_.Height() > fc.Height() {
|
||||
fc = fc_
|
||||
}
|
||||
if fc.Height() == maxHeight {
|
||||
return
|
||||
}
|
||||
}
|
||||
if fc == (FullCommit{}) {
|
||||
err = lerr.ErrCommitNotFound()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ValidatorSet returns validator set at height as provided by the first
|
||||
// provider which has it, or an error otherwise.
|
||||
func (mc *multiProvider) ValidatorSet(chainID string, height int64) (valset *types.ValidatorSet, err error) {
|
||||
for _, p := range mc.providers {
|
||||
valset, err = p.ValidatorSet(chainID, height)
|
||||
if err == nil {
|
||||
// TODO Log unexpected types of errors.
|
||||
return valset, nil
|
||||
}
|
||||
}
|
||||
return nil, lerr.ErrUnknownValidators(chainID, height)
|
||||
}
|
|
@ -1,368 +0,0 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
func TestMemStoreProvidergetByHeightBinaryAndLinearSameResult(t *testing.T) {
|
||||
p := NewMemStoreProvider().(*memStoreProvider)
|
||||
|
||||
// Store a bunch of commits at specific heights
|
||||
// and then ensure that:
|
||||
// * getByHeightLinearSearch
|
||||
// * getByHeightBinarySearch
|
||||
// both return the exact same result
|
||||
|
||||
// 1. Non-existent height commits
|
||||
nonExistent := []int64{-1000, -1, 0, 1, 10, 11, 17, 31, 67, 1000, 1e9}
|
||||
ensureNonExistentCommitsAtHeight(t, "getByHeightLinearSearch", p.getByHeightLinearSearch, nonExistent)
|
||||
ensureNonExistentCommitsAtHeight(t, "getByHeightBinarySearch", p.getByHeightBinarySearch, nonExistent)
|
||||
|
||||
// 2. Save some known height commits
|
||||
knownHeights := []int64{0, 1, 7, 9, 12, 13, 18, 44, 23, 16, 1024, 100, 199, 1e9}
|
||||
createAndStoreCommits(t, p, knownHeights)
|
||||
|
||||
// 3. Now check if those heights are retrieved
|
||||
ensureExistentCommitsAtHeight(t, "getByHeightLinearSearch", p.getByHeightLinearSearch, knownHeights)
|
||||
ensureExistentCommitsAtHeight(t, "getByHeightBinarySearch", p.getByHeightBinarySearch, knownHeights)
|
||||
|
||||
// 4. And now for the height probing to ensure that any height
|
||||
// requested returns a fullCommit of height <= requestedHeight.
|
||||
comparegetByHeightAlgorithms(t, p, 0, 0)
|
||||
comparegetByHeightAlgorithms(t, p, 1, 1)
|
||||
comparegetByHeightAlgorithms(t, p, 2, 1)
|
||||
comparegetByHeightAlgorithms(t, p, 5, 1)
|
||||
comparegetByHeightAlgorithms(t, p, 7, 7)
|
||||
comparegetByHeightAlgorithms(t, p, 10, 9)
|
||||
comparegetByHeightAlgorithms(t, p, 12, 12)
|
||||
comparegetByHeightAlgorithms(t, p, 14, 13)
|
||||
comparegetByHeightAlgorithms(t, p, 19, 18)
|
||||
comparegetByHeightAlgorithms(t, p, 43, 23)
|
||||
comparegetByHeightAlgorithms(t, p, 45, 44)
|
||||
comparegetByHeightAlgorithms(t, p, 1025, 1024)
|
||||
comparegetByHeightAlgorithms(t, p, 101, 100)
|
||||
comparegetByHeightAlgorithms(t, p, 1e3, 199)
|
||||
comparegetByHeightAlgorithms(t, p, 1e4, 1024)
|
||||
comparegetByHeightAlgorithms(t, p, 1e9, 1e9)
|
||||
comparegetByHeightAlgorithms(t, p, 1e9+1, 1e9)
|
||||
}
|
||||
|
||||
func createAndStoreCommits(t *testing.T, p Provider, heights []int64) {
|
||||
chainID := "cache-best-height-binary-and-linear"
|
||||
appHash := []byte("0xdeadbeef")
|
||||
keys := GenValKeys(len(heights) / 2)
|
||||
|
||||
for _, h := range heights {
|
||||
vals := keys.ToValidators(10, int64(len(heights)/2))
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
err := p.StoreCommit(fc)
|
||||
require.NoError(t, err, "StoreCommit height=%d", h)
|
||||
}
|
||||
}
|
||||
|
||||
func comparegetByHeightAlgorithms(t *testing.T, p *memStoreProvider, ask, expect int64) {
|
||||
algos := map[string]func(int64) (FullCommit, error){
|
||||
"getHeightByLinearSearch": p.getByHeightLinearSearch,
|
||||
"getHeightByBinarySearch": p.getByHeightBinarySearch,
|
||||
}
|
||||
|
||||
for algo, fn := range algos {
|
||||
fc, err := fn(ask)
|
||||
// t.Logf("%s got=%v want=%d", algo, expect, fc.Height())
|
||||
require.Nil(t, err, "%s: %+v", algo, err)
|
||||
if assert.Equal(t, expect, fc.Height()) {
|
||||
err = p.StoreCommit(fc)
|
||||
require.Nil(t, err, "%s: %+v", algo, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var blankFullCommit FullCommit
|
||||
|
||||
func ensureNonExistentCommitsAtHeight(t *testing.T, prefix string, fn func(int64) (FullCommit, error), data []int64) {
|
||||
for i, qh := range data {
|
||||
fc, err := fn(qh)
|
||||
assert.NotNil(t, err, "#%d: %s: height=%d should return non-nil error", i, prefix, qh)
|
||||
assert.Equal(t, fc, blankFullCommit, "#%d: %s: height=%d\ngot =%+v\nwant=%+v", i, prefix, qh, fc, blankFullCommit)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureExistentCommitsAtHeight(t *testing.T, prefix string, fn func(int64) (FullCommit, error), data []int64) {
|
||||
for i, qh := range data {
|
||||
fc, err := fn(qh)
|
||||
assert.Nil(t, err, "#%d: %s: height=%d should not return an error: %v", i, prefix, qh, err)
|
||||
assert.NotEqual(t, fc, blankFullCommit, "#%d: %s: height=%d got a blankCommit", i, prefix, qh)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGenCommit20(b *testing.B) {
|
||||
keys := GenValKeys(20)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkGenCommit100(b *testing.B) {
|
||||
keys := GenValKeys(100)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkGenCommitSec20(b *testing.B) {
|
||||
keys := GenSecpValKeys(20)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkGenCommitSec100(b *testing.B) {
|
||||
keys := GenSecpValKeys(100)
|
||||
benchmarkGenCommit(b, keys)
|
||||
}
|
||||
|
||||
func benchmarkGenCommit(b *testing.B, keys ValKeys) {
|
||||
chainID := fmt.Sprintf("bench-%d", len(keys))
|
||||
vals := keys.ToValidators(20, 10)
|
||||
for i := 0; i < b.N; i++ {
|
||||
h := int64(1 + i)
|
||||
appHash := []byte(fmt.Sprintf("h=%d", h))
|
||||
resHash := []byte(fmt.Sprintf("res=%d", h))
|
||||
keys.GenCommit(chainID, h, nil, vals, appHash, []byte("params"), resHash, 0, len(keys))
|
||||
}
|
||||
}
|
||||
|
||||
// this benchmarks generating one key
|
||||
func BenchmarkGenValKeys(b *testing.B) {
|
||||
keys := GenValKeys(20)
|
||||
for i := 0; i < b.N; i++ {
|
||||
keys = keys.Extend(1)
|
||||
}
|
||||
}
|
||||
|
||||
// this benchmarks generating one key
|
||||
func BenchmarkGenSecpValKeys(b *testing.B) {
|
||||
keys := GenSecpValKeys(20)
|
||||
for i := 0; i < b.N; i++ {
|
||||
keys = keys.Extend(1)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToValidators20(b *testing.B) {
|
||||
benchmarkToValidators(b, 20)
|
||||
}
|
||||
|
||||
func BenchmarkToValidators100(b *testing.B) {
|
||||
benchmarkToValidators(b, 100)
|
||||
}
|
||||
|
||||
// this benchmarks constructing the validator set (.PubKey() * nodes)
|
||||
func benchmarkToValidators(b *testing.B, nodes int) {
|
||||
keys := GenValKeys(nodes)
|
||||
for i := 1; i <= b.N; i++ {
|
||||
keys.ToValidators(int64(2*i), int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToValidatorsSec100(b *testing.B) {
|
||||
benchmarkToValidatorsSec(b, 100)
|
||||
}
|
||||
|
||||
// this benchmarks constructing the validator set (.PubKey() * nodes)
|
||||
func benchmarkToValidatorsSec(b *testing.B, nodes int) {
|
||||
keys := GenSecpValKeys(nodes)
|
||||
for i := 1; i <= b.N; i++ {
|
||||
keys.ToValidators(int64(2*i), int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommit20(b *testing.B) {
|
||||
keys := GenValKeys(20)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommit100(b *testing.B) {
|
||||
keys := GenValKeys(100)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommitSec20(b *testing.B) {
|
||||
keys := GenSecpValKeys(20)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func BenchmarkCertifyCommitSec100(b *testing.B) {
|
||||
keys := GenSecpValKeys(100)
|
||||
benchmarkCertifyCommit(b, keys)
|
||||
}
|
||||
|
||||
func benchmarkCertifyCommit(b *testing.B, keys ValKeys) {
|
||||
chainID := "bench-certify"
|
||||
vals := keys.ToValidators(20, 10)
|
||||
cert := NewStaticCertifier(chainID, vals)
|
||||
check := keys.GenCommit(chainID, 123, nil, vals, []byte("foo"), []byte("params"), []byte("res"), 0, len(keys))
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := cert.Certify(check)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type algo bool
|
||||
|
||||
const (
|
||||
linearSearch = true
|
||||
binarySearch = false
|
||||
)
|
||||
|
||||
// Lazy load the commits
|
||||
var fcs5, fcs50, fcs100, fcs500, fcs1000 []FullCommit
|
||||
var h5, h50, h100, h500, h1000 []int64
|
||||
var commitsOnce sync.Once
|
||||
|
||||
func lazyGenerateFullCommits(b *testing.B) {
|
||||
b.Logf("Generating FullCommits")
|
||||
commitsOnce.Do(func() {
|
||||
fcs5, h5 = genFullCommits(nil, nil, 5)
|
||||
b.Logf("Generated 5 FullCommits")
|
||||
fcs50, h50 = genFullCommits(fcs5, h5, 50)
|
||||
b.Logf("Generated 50 FullCommits")
|
||||
fcs100, h100 = genFullCommits(fcs50, h50, 100)
|
||||
b.Logf("Generated 100 FullCommits")
|
||||
fcs500, h500 = genFullCommits(fcs100, h100, 500)
|
||||
b.Logf("Generated 500 FullCommits")
|
||||
fcs1000, h1000 = genFullCommits(fcs500, h500, 1000)
|
||||
b.Logf("Generated 1000 FullCommits")
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch5(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs5, h5, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch50(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs50, h50, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch100(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs100, h100, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch500(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs500, h500, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightLinearSearch1000(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs1000, h1000, linearSearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch5(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs5, h5, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch50(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs50, h50, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch100(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs100, h100, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch500(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs500, h500, binarySearch)
|
||||
}
|
||||
|
||||
func BenchmarkMemStoreProviderGetByHeightBinarySearch1000(b *testing.B) {
|
||||
benchmarkMemStoreProvidergetByHeight(b, fcs1000, h1000, binarySearch)
|
||||
}
|
||||
|
||||
var rng = cmn.NewRand()
|
||||
|
||||
func init() {
|
||||
rng.Seed(10)
|
||||
}
|
||||
|
||||
func benchmarkMemStoreProvidergetByHeight(b *testing.B, fcs []FullCommit, fHeights []int64, algo algo) {
|
||||
lazyGenerateFullCommits(b)
|
||||
|
||||
b.StopTimer()
|
||||
mp := NewMemStoreProvider()
|
||||
for i, fc := range fcs {
|
||||
if err := mp.StoreCommit(fc); err != nil {
|
||||
b.Fatalf("FullCommit #%d: err: %v", i, err)
|
||||
}
|
||||
}
|
||||
qHeights := make([]int64, len(fHeights))
|
||||
copy(qHeights, fHeights)
|
||||
// Append some non-existent heights to trigger the worst cases.
|
||||
qHeights = append(qHeights, 19, -100, -10000, 1e7, -17, 31, -1e9)
|
||||
|
||||
memP := mp.(*memStoreProvider)
|
||||
searchFn := memP.getByHeightLinearSearch
|
||||
if algo == binarySearch { // nolint
|
||||
searchFn = memP.getByHeightBinarySearch
|
||||
}
|
||||
|
||||
hPerm := rng.Perm(len(qHeights))
|
||||
b.StartTimer()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, j := range hPerm {
|
||||
h := qHeights[j]
|
||||
if _, err := searchFn(h); err != nil {
|
||||
}
|
||||
}
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
|
||||
func genFullCommits(prevFC []FullCommit, prevH []int64, want int) ([]FullCommit, []int64) {
|
||||
fcs := make([]FullCommit, len(prevFC))
|
||||
copy(fcs, prevFC)
|
||||
heights := make([]int64, len(prevH))
|
||||
copy(heights, prevH)
|
||||
|
||||
appHash := []byte("benchmarks")
|
||||
chainID := "benchmarks-gen-full-commits"
|
||||
n := want
|
||||
keys := GenValKeys(2 + (n / 3))
|
||||
for i := 0; i < n; i++ {
|
||||
vals := keys.ToValidators(10, int64(n/2))
|
||||
h := int64(20 + 10*i)
|
||||
fcs = append(fcs, keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5))
|
||||
heights = append(heights, h)
|
||||
}
|
||||
return fcs, heights
|
||||
}
|
||||
|
||||
func TestMemStoreProviderLatestCommitAlwaysUsesSorted(t *testing.T) {
|
||||
p := NewMemStoreProvider().(*memStoreProvider)
|
||||
// 1. With no commits yet stored, it should return ErrCommitNotFound
|
||||
got, err := p.LatestCommit()
|
||||
require.Equal(t, err.Error(), liteErr.ErrCommitNotFound().Error(), "should return ErrCommitNotFound()")
|
||||
require.Equal(t, got, blankFullCommit, "With no fullcommits, it should return a blank FullCommit")
|
||||
|
||||
// 2. Generate some full commits now and we'll add them unsorted.
|
||||
genAndStoreCommitsOfHeight(t, p, 27, 100, 1, 12, 1000, 17, 91)
|
||||
fc, err := p.LatestCommit()
|
||||
require.Nil(t, err, "with commits saved no error expected")
|
||||
require.NotEqual(t, fc, blankFullCommit, "with commits saved no blank FullCommit")
|
||||
require.Equal(t, fc.Height(), int64(1000), "the latest commit i.e. the largest expected")
|
||||
}
|
||||
|
||||
func genAndStoreCommitsOfHeight(t *testing.T, p Provider, heights ...int64) {
|
||||
n := len(heights)
|
||||
appHash := []byte("tests")
|
||||
chainID := "tests-gen-full-commits"
|
||||
keys := GenValKeys(2 + (n / 3))
|
||||
for i := 0; i < n; i++ {
|
||||
h := heights[i]
|
||||
vals := keys.ToValidators(10, int64(n/2))
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
err := p.StoreCommit(fc)
|
||||
require.NoError(t, err, "StoreCommit height=%d", h)
|
||||
}
|
||||
}
|
121
lite/provider.go
121
lite/provider.go
|
@ -1,103 +1,32 @@
|
|||
package lite
|
||||
|
||||
// Provider is used to get more validators by other means.
|
||||
//
|
||||
// Examples: MemProvider, files.Provider, client.Provider, CacheProvider....
|
||||
import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
// Provider provides information for the lite client to sync validators.
|
||||
// Examples: MemProvider, files.Provider, client.Provider, CacheProvider.
|
||||
type Provider interface {
|
||||
// StoreCommit saves a FullCommit after we have verified it,
|
||||
// so we can query for it later. Important for updating our
|
||||
// store of trusted commits.
|
||||
StoreCommit(fc FullCommit) error
|
||||
// GetByHeight returns the closest commit with height <= h.
|
||||
GetByHeight(h int64) (FullCommit, error)
|
||||
// GetByHash returns a commit exactly matching this validator hash.
|
||||
GetByHash(hash []byte) (FullCommit, error)
|
||||
// LatestCommit returns the newest commit stored.
|
||||
LatestCommit() (FullCommit, error)
|
||||
|
||||
// LatestFullCommit returns the latest commit with minHeight <= height <=
|
||||
// maxHeight.
|
||||
// If maxHeight is zero, returns the latest where minHeight <= height.
|
||||
LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error)
|
||||
|
||||
// Get the valset that corresponds to chainID and height and return.
|
||||
// Height must be >= 1.
|
||||
ValidatorSet(chainID string, height int64) (*types.ValidatorSet, error)
|
||||
|
||||
// Set a logger.
|
||||
SetLogger(logger log.Logger)
|
||||
}
|
||||
|
||||
// cacheProvider allows you to place one or more caches in front of a source
|
||||
// Provider. It runs through them in order until a match is found.
|
||||
// So you can keep a local cache, and check with the network if
|
||||
// no data is there.
|
||||
type cacheProvider struct {
|
||||
Providers []Provider
|
||||
}
|
||||
// A provider that can also persist new information.
|
||||
// Examples: MemProvider, files.Provider, CacheProvider.
|
||||
type PersistentProvider interface {
|
||||
Provider
|
||||
|
||||
// NewCacheProvider returns a new provider which wraps multiple other providers.
|
||||
func NewCacheProvider(providers ...Provider) Provider {
|
||||
return cacheProvider{
|
||||
Providers: providers,
|
||||
}
|
||||
}
|
||||
|
||||
// StoreCommit tries to add the seed to all providers.
|
||||
//
|
||||
// Aborts on first error it encounters (closest provider)
|
||||
func (c cacheProvider) StoreCommit(fc FullCommit) (err error) {
|
||||
for _, p := range c.Providers {
|
||||
err = p.StoreCommit(fc)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetByHeight should return the closest possible match from all providers.
|
||||
//
|
||||
// The Cache is usually organized in order from cheapest call (memory)
|
||||
// to most expensive calls (disk/network). However, since GetByHeight returns
|
||||
// a FullCommit at h' <= h, if the memory has a seed at h-10, but the network would
|
||||
// give us the exact match, a naive "stop at first non-error" would hide
|
||||
// the actual desired results.
|
||||
//
|
||||
// Thus, we query each provider in order until we find an exact match
|
||||
// or we finished querying them all. If at least one returned a non-error,
|
||||
// then this returns the best match (minimum h-h').
|
||||
func (c cacheProvider) GetByHeight(h int64) (fc FullCommit, err error) {
|
||||
for _, p := range c.Providers {
|
||||
var tfc FullCommit
|
||||
tfc, err = p.GetByHeight(h)
|
||||
if err == nil {
|
||||
if tfc.Height() > fc.Height() {
|
||||
fc = tfc
|
||||
}
|
||||
if tfc.Height() == h {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// even if the last one had an error, if any was a match, this is good
|
||||
if fc.Height() > 0 {
|
||||
err = nil
|
||||
}
|
||||
return fc, err
|
||||
}
|
||||
|
||||
// GetByHash returns the FullCommit for the hash or an error if the commit is not found.
|
||||
func (c cacheProvider) GetByHash(hash []byte) (fc FullCommit, err error) {
|
||||
for _, p := range c.Providers {
|
||||
fc, err = p.GetByHash(hash)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return fc, err
|
||||
}
|
||||
|
||||
// LatestCommit returns the latest FullCommit or an error if no commit exists.
|
||||
func (c cacheProvider) LatestCommit() (fc FullCommit, err error) {
|
||||
for _, p := range c.Providers {
|
||||
var tfc FullCommit
|
||||
tfc, err = p.LatestCommit()
|
||||
if err == nil && tfc.Height() > fc.Height() {
|
||||
fc = tfc
|
||||
}
|
||||
}
|
||||
// even if the last one had an error, if any was a match, this is good
|
||||
if fc.Height() > 0 {
|
||||
err = nil
|
||||
}
|
||||
return fc, err
|
||||
// SaveFullCommit saves a FullCommit (without verification).
|
||||
SaveFullCommit(fc FullCommit) error
|
||||
}
|
||||
|
|
|
@ -1,98 +1,90 @@
|
|||
// nolint: vetshadow
|
||||
package lite_test
|
||||
package lite
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
lerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
// missingProvider doesn't store anything, always a miss
|
||||
// Designed as a mock for testing
|
||||
// missingProvider doesn't store anything, always a miss.
|
||||
// Designed as a mock for testing.
|
||||
type missingProvider struct{}
|
||||
|
||||
// NewMissingProvider returns a provider which does not store anything and always misses.
|
||||
func NewMissingProvider() lite.Provider {
|
||||
func NewMissingProvider() PersistentProvider {
|
||||
return missingProvider{}
|
||||
}
|
||||
|
||||
func (missingProvider) StoreCommit(lite.FullCommit) error { return nil }
|
||||
func (missingProvider) GetByHeight(int64) (lite.FullCommit, error) {
|
||||
return lite.FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
func (missingProvider) SaveFullCommit(FullCommit) error { return nil }
|
||||
func (missingProvider) LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error) {
|
||||
return FullCommit{}, lerr.ErrCommitNotFound()
|
||||
}
|
||||
func (missingProvider) GetByHash([]byte) (lite.FullCommit, error) {
|
||||
return lite.FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
}
|
||||
func (missingProvider) LatestCommit() (lite.FullCommit, error) {
|
||||
return lite.FullCommit{}, liteErr.ErrCommitNotFound()
|
||||
func (missingProvider) ValidatorSet(chainID string, height int64) (*types.ValidatorSet, error) {
|
||||
return nil, errors.New("missing validator set")
|
||||
}
|
||||
func (missingProvider) SetLogger(_ log.Logger) {}
|
||||
|
||||
func TestMemProvider(t *testing.T) {
|
||||
p := lite.NewMemStoreProvider()
|
||||
p := NewDBProvider("mem", dbm.NewMemDB())
|
||||
checkProvider(t, p, "test-mem", "empty")
|
||||
}
|
||||
|
||||
func TestCacheProvider(t *testing.T) {
|
||||
p := lite.NewCacheProvider(
|
||||
func TestMultiProvider(t *testing.T) {
|
||||
p := NewMultiProvider(
|
||||
NewMissingProvider(),
|
||||
lite.NewMemStoreProvider(),
|
||||
NewDBProvider("mem", dbm.NewMemDB()),
|
||||
NewMissingProvider(),
|
||||
)
|
||||
checkProvider(t, p, "test-cache", "kjfhekfhkewhgit")
|
||||
}
|
||||
|
||||
func checkProvider(t *testing.T, p lite.Provider, chainID, app string) {
|
||||
func checkProvider(t *testing.T, p PersistentProvider, chainID, app string) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
appHash := []byte(app)
|
||||
keys := lite.GenValKeys(5)
|
||||
keys := genPrivKeys(5)
|
||||
count := 10
|
||||
|
||||
// make a bunch of commits...
|
||||
commits := make([]lite.FullCommit, count)
|
||||
// Make a bunch of full commits.
|
||||
fcz := make([]FullCommit, count)
|
||||
for i := 0; i < count; i++ {
|
||||
// two commits for each validator, to check how we handle dups
|
||||
// (10, 0), (10, 1), (10, 1), (10, 2), (10, 2), ...
|
||||
vals := keys.ToValidators(10, int64(count/2))
|
||||
h := int64(20 + 10*i)
|
||||
commits[i] = keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
fcz[i] = keys.GenFullCommit(chainID, h, nil, vals, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
}
|
||||
|
||||
// check provider is empty
|
||||
fc, err := p.GetByHeight(20)
|
||||
// Check that provider is initially empty.
|
||||
fc, err := p.LatestFullCommit(chainID, 1, 1<<63-1)
|
||||
require.NotNil(err)
|
||||
assert.True(liteErr.IsCommitNotFoundErr(err))
|
||||
assert.True(lerr.IsErrCommitNotFound(err))
|
||||
|
||||
fc, err = p.GetByHash(commits[3].ValidatorsHash())
|
||||
require.NotNil(err)
|
||||
assert.True(liteErr.IsCommitNotFoundErr(err))
|
||||
|
||||
// now add them all to the provider
|
||||
for _, s := range commits {
|
||||
err = p.StoreCommit(s)
|
||||
// Save all full commits to the provider.
|
||||
for _, fc := range fcz {
|
||||
err = p.SaveFullCommit(fc)
|
||||
require.Nil(err)
|
||||
// and make sure we can get it back
|
||||
s2, err := p.GetByHash(s.ValidatorsHash())
|
||||
// Make sure we can get it back.
|
||||
fc2, err := p.LatestFullCommit(chainID, fc.Height(), fc.Height())
|
||||
assert.Nil(err)
|
||||
assert.Equal(s, s2)
|
||||
// by height as well
|
||||
s2, err = p.GetByHeight(s.Height())
|
||||
assert.Nil(err)
|
||||
assert.Equal(s, s2)
|
||||
assert.Equal(fc.SignedHeader, fc2.SignedHeader)
|
||||
assert.Equal(fc.Validators, fc2.Validators)
|
||||
assert.Equal(fc.NextValidators, fc2.NextValidators)
|
||||
}
|
||||
|
||||
// make sure we get the last hash if we overstep
|
||||
fc, err = p.GetByHeight(5000)
|
||||
// Make sure we get the last hash if we overstep.
|
||||
fc, err = p.LatestFullCommit(chainID, 1, 5000)
|
||||
if assert.Nil(err) {
|
||||
assert.Equal(commits[count-1].Height(), fc.Height())
|
||||
assert.Equal(commits[count-1], fc)
|
||||
assert.Equal(fcz[count-1].Height(), fc.Height())
|
||||
assert.Equal(fcz[count-1], fc)
|
||||
}
|
||||
|
||||
// and middle ones as well
|
||||
fc, err = p.GetByHeight(47)
|
||||
// ... and middle ones as well.
|
||||
fc, err = p.LatestFullCommit(chainID, 1, 47)
|
||||
if assert.Nil(err) {
|
||||
// we only step by 10, so 40 must be the one below this
|
||||
assert.EqualValues(40, fc.Height())
|
||||
|
@ -100,50 +92,49 @@ func checkProvider(t *testing.T, p lite.Provider, chainID, app string) {
|
|||
|
||||
}
|
||||
|
||||
// this will make a get height, and if it is good, set the data as well
|
||||
func checkGetHeight(t *testing.T, p lite.Provider, ask, expect int64) {
|
||||
fc, err := p.GetByHeight(ask)
|
||||
require.Nil(t, err, "GetByHeight")
|
||||
// This will make a get height, and if it is good, set the data as well.
|
||||
func checkLatestFullCommit(t *testing.T, p PersistentProvider, chainID string, ask, expect int64) {
|
||||
fc, err := p.LatestFullCommit(chainID, 1, ask)
|
||||
require.Nil(t, err)
|
||||
if assert.Equal(t, expect, fc.Height()) {
|
||||
err = p.StoreCommit(fc)
|
||||
require.Nil(t, err, "StoreCommit")
|
||||
err = p.SaveFullCommit(fc)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheGetsBestHeight(t *testing.T) {
|
||||
// assert, require := assert.New(t), require.New(t)
|
||||
func TestMultiLatestFullCommit(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// we will write data to the second level of the cache (p2),
|
||||
// and see what gets cached, stored in
|
||||
p := lite.NewMemStoreProvider()
|
||||
p2 := lite.NewMemStoreProvider()
|
||||
cp := lite.NewCacheProvider(p, p2)
|
||||
// We will write data to the second level of the cache (p2), and see what
|
||||
// gets cached/stored in.
|
||||
p := NewDBProvider("mem1", dbm.NewMemDB())
|
||||
p2 := NewDBProvider("mem2", dbm.NewMemDB())
|
||||
cp := NewMultiProvider(p, p2)
|
||||
|
||||
chainID := "cache-best-height"
|
||||
appHash := []byte("01234567")
|
||||
keys := lite.GenValKeys(5)
|
||||
keys := genPrivKeys(5)
|
||||
count := 10
|
||||
|
||||
// set a bunch of commits
|
||||
// Set a bunch of full commits.
|
||||
for i := 0; i < count; i++ {
|
||||
vals := keys.ToValidators(10, int64(count/2))
|
||||
h := int64(10 * (i + 1))
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
err := p2.StoreCommit(fc)
|
||||
fc := keys.GenFullCommit(chainID, h, nil, vals, vals, appHash, []byte("params"), []byte("results"), 0, 5)
|
||||
err := p2.SaveFullCommit(fc)
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// let's get a few heights from the cache and set them proper
|
||||
checkGetHeight(t, cp, 57, 50)
|
||||
checkGetHeight(t, cp, 33, 30)
|
||||
// Get a few heights from the cache and set them proper.
|
||||
checkLatestFullCommit(t, cp, chainID, 57, 50)
|
||||
checkLatestFullCommit(t, cp, chainID, 33, 30)
|
||||
|
||||
// make sure they are set in p as well (but nothing else)
|
||||
checkGetHeight(t, p, 44, 30)
|
||||
checkGetHeight(t, p, 50, 50)
|
||||
checkGetHeight(t, p, 99, 50)
|
||||
checkLatestFullCommit(t, p, chainID, 44, 30)
|
||||
checkLatestFullCommit(t, p, chainID, 50, 50)
|
||||
checkLatestFullCommit(t, p, chainID, 99, 50)
|
||||
|
||||
// now, query the cache for a higher value
|
||||
checkGetHeight(t, p2, 99, 90)
|
||||
checkGetHeight(t, cp, 99, 90)
|
||||
checkLatestFullCommit(t, p2, chainID, 99, 90)
|
||||
checkLatestFullCommit(t, cp, chainID, 99, 90)
|
||||
}
|
||||
|
|
|
@ -2,27 +2,24 @@ package proxy
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
certerr "github.com/tendermint/tendermint/lite/errors"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func ValidateBlockMeta(meta *types.BlockMeta, check lite.Commit) error {
|
||||
func ValidateBlockMeta(meta *types.BlockMeta, sh types.SignedHeader) error {
|
||||
if meta == nil {
|
||||
return errors.New("expecting a non-nil BlockMeta")
|
||||
}
|
||||
// TODO: check the BlockID??
|
||||
return ValidateHeader(&meta.Header, check)
|
||||
return ValidateHeader(&meta.Header, sh)
|
||||
}
|
||||
|
||||
func ValidateBlock(meta *types.Block, check lite.Commit) error {
|
||||
func ValidateBlock(meta *types.Block, sh types.SignedHeader) error {
|
||||
if meta == nil {
|
||||
return errors.New("expecting a non-nil Block")
|
||||
}
|
||||
err := ValidateHeader(&meta.Header, check)
|
||||
err := ValidateHeader(&meta.Header, sh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -32,17 +29,19 @@ func ValidateBlock(meta *types.Block, check lite.Commit) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func ValidateHeader(head *types.Header, check lite.Commit) error {
|
||||
func ValidateHeader(head *types.Header, sh types.SignedHeader) error {
|
||||
if head == nil {
|
||||
return errors.New("expecting a non-nil Header")
|
||||
}
|
||||
// make sure they are for the same height (obvious fail)
|
||||
if head.Height != check.Height() {
|
||||
return certerr.ErrHeightMismatch(head.Height, check.Height())
|
||||
if sh.Header == nil {
|
||||
return errors.New("unexpected empty SignedHeader")
|
||||
}
|
||||
// check if they are equal by using hashes
|
||||
chead := check.Header
|
||||
if !bytes.Equal(head.Hash(), chead.Hash()) {
|
||||
// Make sure they are for the same height (obvious fail).
|
||||
if head.Height != sh.Height {
|
||||
return errors.New("Header heights mismatched")
|
||||
}
|
||||
// Check if they are equal by using hashes.
|
||||
if !bytes.Equal(head.Hash(), sh.Hash()) {
|
||||
return errors.New("Headers don't match")
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
certclient "github.com/tendermint/tendermint/lite/client"
|
||||
"github.com/tendermint/tendermint/lite/files"
|
||||
)
|
||||
|
||||
func GetCertifier(chainID, rootDir, nodeAddr string) (*lite.InquiringCertifier, error) {
|
||||
trust := lite.NewCacheProvider(
|
||||
lite.NewMemStoreProvider(),
|
||||
files.NewProvider(rootDir),
|
||||
)
|
||||
|
||||
source := certclient.NewHTTPProvider(nodeAddr)
|
||||
|
||||
// XXX: total insecure hack to avoid `init`
|
||||
fc, err := source.LatestCommit()
|
||||
/* XXX
|
||||
// this gets the most recent verified commit
|
||||
fc, err := trust.LatestCommit()
|
||||
if certerr.IsCommitNotFoundErr(err) {
|
||||
return nil, errors.New("Please run init first to establish a root of trust")
|
||||
}*/
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := lite.NewInquiringCertifier(chainID, fc, trust, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
|
@ -1,22 +1,24 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
)
|
||||
|
||||
//--------------------------------------------
|
||||
type errNoData struct{}
|
||||
|
||||
var errNoData = fmt.Errorf("No data returned for query")
|
||||
func (e errNoData) Error() string {
|
||||
return "No data returned for query"
|
||||
}
|
||||
|
||||
// IsNoDataErr checks whether an error is due to a query returning empty data
|
||||
func IsNoDataErr(err error) bool {
|
||||
return errors.Cause(err) == errNoData
|
||||
// IsErrNoData checks whether an error is due to a query returning empty data
|
||||
func IsErrNoData(err error) bool {
|
||||
if err_, ok := err.(cmn.Error); ok {
|
||||
_, ok := err_.Data().(errNoData)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ErrNoData() error {
|
||||
return errors.WithStack(errNoData)
|
||||
return cmn.ErrorWrap(errNoData{}, "")
|
||||
}
|
||||
|
||||
//--------------------------------------------
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestErrorNoData(t *testing.T) {
|
||||
e1 := ErrNoData()
|
||||
assert.True(t, IsNoDataErr(e1))
|
||||
|
||||
e2 := errors.New("foobar")
|
||||
assert.False(t, IsNoDataErr(e2))
|
||||
assert.False(t, IsNoDataErr(nil))
|
||||
}
|
|
@ -1,15 +1,16 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
"github.com/tendermint/tendermint/lite/client"
|
||||
certerr "github.com/tendermint/tendermint/lite/errors"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// KeyProof represents a proof of existence or absence of a single key.
|
||||
|
@ -27,13 +28,13 @@ type KeyProof interface {
|
|||
}
|
||||
|
||||
// GetWithProof will query the key on the given node, and verify it has
|
||||
// a valid proof, as defined by the certifier.
|
||||
// a valid proof, as defined by the Verifier.
|
||||
//
|
||||
// If there is any error in checking, returns an error.
|
||||
// If val is non-empty, proof should be KeyExistsProof
|
||||
// If val is empty, proof should be KeyMissingProof
|
||||
func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client,
|
||||
cert lite.Certifier) (
|
||||
cert lite.Verifier) (
|
||||
val cmn.HexBytes, height int64, proof KeyProof, err error) {
|
||||
|
||||
if reqHeight < 0 {
|
||||
|
@ -53,7 +54,7 @@ func GetWithProof(key []byte, reqHeight int64, node rpcclient.Client,
|
|||
|
||||
// GetWithProofOptions is useful if you want full access to the ABCIQueryOptions
|
||||
func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOptions,
|
||||
node rpcclient.Client, cert lite.Certifier) (
|
||||
node rpcclient.Client, cert lite.Verifier) (
|
||||
*ctypes.ResultABCIQuery, KeyProof, error) {
|
||||
|
||||
_resp, err := node.ABCIQueryWithOptions(path, key, opts)
|
||||
|
@ -75,12 +76,12 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
|
|||
}
|
||||
|
||||
// AppHash for height H is in header H+1
|
||||
commit, err := GetCertifiedCommit(resp.Height+1, node, cert)
|
||||
signedHeader, err := GetCertifiedCommit(resp.Height+1, node, cert)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_ = commit
|
||||
_ = signedHeader
|
||||
return &ctypes.ResultABCIQuery{Response: resp}, nil, nil
|
||||
|
||||
/* // TODO refactor so iavl stuff is not in tendermint core
|
||||
|
@ -98,7 +99,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
|
|||
}
|
||||
|
||||
// Validate the proof against the certified header to ensure data integrity.
|
||||
err = eproof.Verify(resp.Key, resp.Value, commit.Header.AppHash)
|
||||
err = eproof.Verify(resp.Key, resp.Value, signedHeader.AppHash)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Couldn't verify proof")
|
||||
}
|
||||
|
@ -117,7 +118,7 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
|
|||
}
|
||||
|
||||
// Validate the proof against the certified header to ensure data integrity.
|
||||
err = aproof.Verify(resp.Key, nil, commit.Header.AppHash)
|
||||
err = aproof.Verify(resp.Key, nil, signedHeader.AppHash)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Couldn't verify proof")
|
||||
}
|
||||
|
@ -125,28 +126,29 @@ func GetWithProofOptions(path string, key []byte, opts rpcclient.ABCIQueryOption
|
|||
*/
|
||||
}
|
||||
|
||||
// GetCertifiedCommit gets the signed header for a given height
|
||||
// and certifies it. Returns error if unable to get a proven header.
|
||||
func GetCertifiedCommit(h int64, node rpcclient.Client, cert lite.Certifier) (lite.Commit, error) {
|
||||
// GetCertifiedCommit gets the signed header for a given height and certifies
|
||||
// it. Returns error if unable to get a proven header.
|
||||
func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Verifier) (types.SignedHeader, error) {
|
||||
|
||||
// FIXME: cannot use cert.GetByHeight for now, as it also requires
|
||||
// Validators and will fail on querying tendermint for non-current height.
|
||||
// When this is supported, we should use it instead...
|
||||
rpcclient.WaitForHeight(node, h, nil)
|
||||
cresp, err := node.Commit(&h)
|
||||
rpcclient.WaitForHeight(client, h, nil)
|
||||
cresp, err := client.Commit(&h)
|
||||
if err != nil {
|
||||
return lite.Commit{}, err
|
||||
return types.SignedHeader{}, err
|
||||
}
|
||||
|
||||
commit := client.CommitFromResult(cresp)
|
||||
// validate downloaded checkpoint with our request and trust store.
|
||||
if commit.Height() != h {
|
||||
return lite.Commit{}, certerr.ErrHeightMismatch(h, commit.Height())
|
||||
// Validate downloaded checkpoint with our request and trust store.
|
||||
sh := cresp.SignedHeader
|
||||
if sh.Height != h {
|
||||
return types.SignedHeader{}, fmt.Errorf("height mismatch: want %v got %v",
|
||||
h, sh.Height)
|
||||
}
|
||||
|
||||
if err = cert.Certify(commit); err != nil {
|
||||
return lite.Commit{}, err
|
||||
if err = cert.Certify(sh); err != nil {
|
||||
return types.SignedHeader{}, err
|
||||
}
|
||||
|
||||
return commit, nil
|
||||
return sh, nil
|
||||
}
|
||||
|
|
|
@ -19,12 +19,12 @@ import (
|
|||
)
|
||||
|
||||
var node *nm.Node
|
||||
var chainID = "tendermint_test" // TODO use from config.
|
||||
|
||||
// TODO fix tests!!
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
app := kvstore.NewKVStoreApplication()
|
||||
|
||||
node = rpctest.StartTendermint(app)
|
||||
|
||||
code := m.Run()
|
||||
|
@ -55,28 +55,28 @@ func _TestAppProofs(t *testing.T) {
|
|||
brh := br.Height
|
||||
|
||||
// This sets up our trust on the node based on some past point.
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(brh - 2)
|
||||
source := certclient.NewProvider(chainID, cl)
|
||||
seed, err := source.LatestFullCommit(chainID, brh-2, brh-2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := lite.NewStaticCertifier("my-chain", seed.Validators)
|
||||
cert := lite.NewBaseVerifier("my-chain", seed.Height(), seed.Validators)
|
||||
|
||||
client.WaitForHeight(cl, 3, nil)
|
||||
latest, err := source.LatestCommit()
|
||||
latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1)
|
||||
require.NoError(err, "%+v", err)
|
||||
rootHash := latest.Header.AppHash
|
||||
rootHash := latest.SignedHeader.AppHash
|
||||
|
||||
// verify a query before the tx block has no data (and valid non-exist proof)
|
||||
bs, height, proof, err := GetWithProof(k, brh-1, cl, cert)
|
||||
fmt.Println(bs, height, proof, err)
|
||||
require.NotNil(err)
|
||||
require.True(IsNoDataErr(err), err.Error())
|
||||
require.True(IsErrNoData(err), err.Error())
|
||||
require.Nil(bs)
|
||||
|
||||
// but given that block it is good
|
||||
bs, height, proof, err = GetWithProof(k, brh, cl, cert)
|
||||
require.NoError(err, "%+v", err)
|
||||
require.NotNil(proof)
|
||||
require.True(height >= int64(latest.Header.Height))
|
||||
require.True(height >= int64(latest.Height()))
|
||||
|
||||
// Alexis there is a bug here, somehow the above code gives us rootHash = nil
|
||||
// and proof.Verify doesn't care, while proofNotExists.Verify fails.
|
||||
|
@ -92,7 +92,7 @@ func _TestAppProofs(t *testing.T) {
|
|||
// Test non-existing key.
|
||||
missing := []byte("my-missing-key")
|
||||
bs, _, proof, err = GetWithProof(missing, 0, cl, cert)
|
||||
require.True(IsNoDataErr(err))
|
||||
require.True(IsErrNoData(err))
|
||||
require.Nil(bs)
|
||||
require.NotNil(proof)
|
||||
err = proof.Verify(missing, nil, rootHash)
|
||||
|
@ -114,10 +114,10 @@ func _TestTxProofs(t *testing.T) {
|
|||
require.EqualValues(0, br.DeliverTx.Code)
|
||||
brh := br.Height
|
||||
|
||||
source := certclient.NewProvider(cl)
|
||||
seed, err := source.GetByHeight(brh - 2)
|
||||
source := certclient.NewProvider(chainID, cl)
|
||||
seed, err := source.LatestFullCommit(chainID, brh-2, brh-2)
|
||||
require.NoError(err, "%+v", err)
|
||||
cert := lite.NewStaticCertifier("my-chain", seed.Validators)
|
||||
cert := lite.NewBaseVerifier("my-chain", seed.Height(), seed.Validators)
|
||||
|
||||
// First let's make sure a bogus transaction hash returns a valid non-existence proof.
|
||||
key := types.Tx([]byte("bogus")).Hash()
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
"github.com/tendermint/tendermint/lite/proxy"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
@ -26,40 +25,40 @@ var hdrHeight11 = types.Header{
|
|||
|
||||
func TestValidateBlock(t *testing.T) {
|
||||
tests := []struct {
|
||||
block *types.Block
|
||||
commit lite.Commit
|
||||
wantErr string
|
||||
block *types.Block
|
||||
signedHeader types.SignedHeader
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
block: nil, wantErr: "non-nil Block",
|
||||
},
|
||||
{
|
||||
block: &types.Block{},
|
||||
block: &types.Block{}, wantErr: "unexpected empty SignedHeader",
|
||||
},
|
||||
|
||||
// Start Header.Height mismatch test
|
||||
{
|
||||
block: &types.Block{Header: types.Header{Height: 10}},
|
||||
commit: lite.Commit{Header: &types.Header{Height: 11}},
|
||||
wantErr: "don't match - 10 vs 11",
|
||||
block: &types.Block{Header: types.Header{Height: 10}},
|
||||
signedHeader: types.SignedHeader{Header: &types.Header{Height: 11}},
|
||||
wantErr: "Header heights mismatched",
|
||||
},
|
||||
|
||||
{
|
||||
block: &types.Block{Header: types.Header{Height: 11}},
|
||||
commit: lite.Commit{Header: &types.Header{Height: 11}},
|
||||
block: &types.Block{Header: types.Header{Height: 11}},
|
||||
signedHeader: types.SignedHeader{Header: &types.Header{Height: 11}},
|
||||
},
|
||||
// End Header.Height mismatch test
|
||||
|
||||
// Start Header.Hash mismatch test
|
||||
{
|
||||
block: &types.Block{Header: hdrHeight11},
|
||||
commit: lite.Commit{Header: &types.Header{Height: 11}},
|
||||
wantErr: "Headers don't match",
|
||||
block: &types.Block{Header: hdrHeight11},
|
||||
signedHeader: types.SignedHeader{Header: &types.Header{Height: 11}},
|
||||
wantErr: "Headers don't match",
|
||||
},
|
||||
|
||||
{
|
||||
block: &types.Block{Header: hdrHeight11},
|
||||
commit: lite.Commit{Header: &hdrHeight11},
|
||||
block: &types.Block{Header: hdrHeight11},
|
||||
signedHeader: types.SignedHeader{Header: &hdrHeight11},
|
||||
},
|
||||
// End Header.Hash mismatch test
|
||||
|
||||
|
@ -69,7 +68,7 @@ func TestValidateBlock(t *testing.T) {
|
|||
Header: types.Header{Height: 11},
|
||||
Data: types.Data{Txs: []types.Tx{[]byte("0xDE"), []byte("AD")}},
|
||||
},
|
||||
commit: lite.Commit{
|
||||
signedHeader: types.SignedHeader{
|
||||
Header: &types.Header{Height: 11},
|
||||
Commit: &types.Commit{BlockID: types.BlockID{Hash: []byte("0xDEADBEEF")}},
|
||||
},
|
||||
|
@ -80,7 +79,7 @@ func TestValidateBlock(t *testing.T) {
|
|||
Header: types.Header{Height: 11, DataHash: deadBeefHash},
|
||||
Data: types.Data{Txs: deadBeefTxs},
|
||||
},
|
||||
commit: lite.Commit{
|
||||
signedHeader: types.SignedHeader{
|
||||
Header: &types.Header{Height: 11},
|
||||
Commit: &types.Commit{BlockID: types.BlockID{Hash: []byte("DEADBEEF")}},
|
||||
},
|
||||
|
@ -89,7 +88,7 @@ func TestValidateBlock(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
err := proxy.ValidateBlock(tt.block, tt.commit)
|
||||
err := proxy.ValidateBlock(tt.block, tt.signedHeader)
|
||||
if tt.wantErr != "" {
|
||||
if err == nil {
|
||||
assert.FailNowf(t, "Unexpectedly passed", "#%d", i)
|
||||
|
@ -105,40 +104,40 @@ func TestValidateBlock(t *testing.T) {
|
|||
|
||||
func TestValidateBlockMeta(t *testing.T) {
|
||||
tests := []struct {
|
||||
meta *types.BlockMeta
|
||||
commit lite.Commit
|
||||
wantErr string
|
||||
meta *types.BlockMeta
|
||||
signedHeader types.SignedHeader
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
meta: nil, wantErr: "non-nil BlockMeta",
|
||||
},
|
||||
{
|
||||
meta: &types.BlockMeta{},
|
||||
meta: &types.BlockMeta{}, wantErr: "unexpected empty SignedHeader",
|
||||
},
|
||||
|
||||
// Start Header.Height mismatch test
|
||||
{
|
||||
meta: &types.BlockMeta{Header: types.Header{Height: 10}},
|
||||
commit: lite.Commit{Header: &types.Header{Height: 11}},
|
||||
wantErr: "don't match - 10 vs 11",
|
||||
meta: &types.BlockMeta{Header: types.Header{Height: 10}},
|
||||
signedHeader: types.SignedHeader{Header: &types.Header{Height: 11}},
|
||||
wantErr: "Header heights mismatched",
|
||||
},
|
||||
|
||||
{
|
||||
meta: &types.BlockMeta{Header: types.Header{Height: 11}},
|
||||
commit: lite.Commit{Header: &types.Header{Height: 11}},
|
||||
meta: &types.BlockMeta{Header: types.Header{Height: 11}},
|
||||
signedHeader: types.SignedHeader{Header: &types.Header{Height: 11}},
|
||||
},
|
||||
// End Header.Height mismatch test
|
||||
|
||||
// Start Headers don't match test
|
||||
{
|
||||
meta: &types.BlockMeta{Header: hdrHeight11},
|
||||
commit: lite.Commit{Header: &types.Header{Height: 11}},
|
||||
wantErr: "Headers don't match",
|
||||
meta: &types.BlockMeta{Header: hdrHeight11},
|
||||
signedHeader: types.SignedHeader{Header: &types.Header{Height: 11}},
|
||||
wantErr: "Headers don't match",
|
||||
},
|
||||
|
||||
{
|
||||
meta: &types.BlockMeta{Header: hdrHeight11},
|
||||
commit: lite.Commit{Header: &hdrHeight11},
|
||||
meta: &types.BlockMeta{Header: hdrHeight11},
|
||||
signedHeader: types.SignedHeader{Header: &hdrHeight11},
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -150,7 +149,7 @@ func TestValidateBlockMeta(t *testing.T) {
|
|||
Time: testTime1,
|
||||
},
|
||||
},
|
||||
commit: lite.Commit{
|
||||
signedHeader: types.SignedHeader{
|
||||
Header: &types.Header{Height: 11, DataHash: deadBeefHash},
|
||||
},
|
||||
wantErr: "Headers don't match",
|
||||
|
@ -164,7 +163,7 @@ func TestValidateBlockMeta(t *testing.T) {
|
|||
Time: testTime1,
|
||||
},
|
||||
},
|
||||
commit: lite.Commit{
|
||||
signedHeader: types.SignedHeader{
|
||||
Header: &types.Header{
|
||||
Height: 11, DataHash: deadBeefHash,
|
||||
ValidatorsHash: []byte("Tendermint"),
|
||||
|
@ -183,7 +182,7 @@ func TestValidateBlockMeta(t *testing.T) {
|
|||
Time: testTime2,
|
||||
},
|
||||
},
|
||||
commit: lite.Commit{
|
||||
signedHeader: types.SignedHeader{
|
||||
Header: &types.Header{
|
||||
Height: 11, DataHash: deadBeefHash,
|
||||
ValidatorsHash: []byte("Tendermint-x"),
|
||||
|
@ -197,7 +196,7 @@ func TestValidateBlockMeta(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
err := proxy.ValidateBlockMeta(tt.meta, tt.commit)
|
||||
err := proxy.ValidateBlockMeta(tt.meta, tt.signedHeader)
|
||||
if tt.wantErr != "" {
|
||||
if err == nil {
|
||||
assert.FailNowf(t, "Unexpectedly passed", "#%d: wanted error %q", i, tt.wantErr)
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
lclient "github.com/tendermint/tendermint/lite/client"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
log "github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
func NewVerifier(chainID, rootDir string, client lclient.SignStatusClient, logger log.Logger) (*lite.DynamicVerifier, error) {
|
||||
|
||||
logger = logger.With("module", "lite/proxy")
|
||||
logger.Info("lite/proxy/NewVerifier()...", "chainID", chainID, "rootDir", rootDir, "client", client)
|
||||
|
||||
memProvider := lite.NewDBProvider("trusted.mem", dbm.NewMemDB()).SetLimit(10)
|
||||
lvlProvider := lite.NewDBProvider("trusted.lvl", dbm.NewDB("trust-base", dbm.LevelDBBackend, rootDir))
|
||||
trust := lite.NewMultiProvider(
|
||||
memProvider,
|
||||
lvlProvider,
|
||||
)
|
||||
source := lclient.NewProvider(chainID, client)
|
||||
cert := lite.NewDynamicVerifier(chainID, trust, source)
|
||||
cert.SetLogger(logger) // Sets logger recursively.
|
||||
|
||||
// TODO: Make this more secure, e.g. make it interactive in the console?
|
||||
_, err := trust.LatestFullCommit(chainID, 1, 1<<63-1)
|
||||
if err != nil {
|
||||
logger.Info("lite/proxy/NewVerifier found no trusted full commit, initializing from source from height 1...")
|
||||
fc, err := source.LatestFullCommit(chainID, 1, 1)
|
||||
if err != nil {
|
||||
return nil, cmn.ErrorWrap(err, "fetching source full commit @ height 1")
|
||||
}
|
||||
err = trust.SaveFullCommit(fc)
|
||||
if err != nil {
|
||||
return nil, cmn.ErrorWrap(err, "saving full commit to trusted")
|
||||
}
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
|
@ -4,25 +4,24 @@ import (
|
|||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
certclient "github.com/tendermint/tendermint/lite/client"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
var _ rpcclient.Client = Wrapper{}
|
||||
|
||||
// Wrapper wraps a rpcclient with a Certifier and double-checks any input that is
|
||||
// Wrapper wraps a rpcclient with a Verifier and double-checks any input that is
|
||||
// provable before passing it along. Allows you to make any rpcclient fully secure.
|
||||
type Wrapper struct {
|
||||
rpcclient.Client
|
||||
cert *lite.InquiringCertifier
|
||||
cert *lite.DynamicVerifier
|
||||
}
|
||||
|
||||
// SecureClient uses a given certifier to wrap an connection to an untrusted
|
||||
// SecureClient uses a given Verifier to wrap an connection to an untrusted
|
||||
// host and return a cryptographically secure rpc client.
|
||||
//
|
||||
// If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface
|
||||
func SecureClient(c rpcclient.Client, cert *lite.InquiringCertifier) Wrapper {
|
||||
func SecureClient(c rpcclient.Client, cert *lite.DynamicVerifier) Wrapper {
|
||||
wrap := Wrapper{c, cert}
|
||||
// TODO: no longer possible as no more such interface exposed....
|
||||
// if we wrap http client, then we can swap out the event switch to filter
|
||||
|
@ -53,11 +52,11 @@ func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) {
|
|||
return res, err
|
||||
}
|
||||
h := int64(res.Height)
|
||||
check, err := GetCertifiedCommit(h, w.Client, w.cert)
|
||||
sh, err := GetCertifiedCommit(h, w.Client, w.cert)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
err = res.Proof.Validate(check.Header.DataHash)
|
||||
err = res.Proof.Validate(sh.DataHash)
|
||||
return res, err
|
||||
}
|
||||
|
||||
|
@ -74,12 +73,12 @@ func (w Wrapper) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlock
|
|||
// go and verify every blockmeta in the result....
|
||||
for _, meta := range r.BlockMetas {
|
||||
// get a checkpoint to verify from
|
||||
c, err := w.Commit(&meta.Header.Height)
|
||||
res, err := w.Commit(&meta.Header.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
check := certclient.CommitFromResult(c)
|
||||
err = ValidateBlockMeta(meta, check)
|
||||
sh := res.SignedHeader
|
||||
err = ValidateBlockMeta(meta, sh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -90,41 +89,57 @@ func (w Wrapper) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlock
|
|||
|
||||
// Block returns an entire block and verifies all signatures
|
||||
func (w Wrapper) Block(height *int64) (*ctypes.ResultBlock, error) {
|
||||
r, err := w.Client.Block(height)
|
||||
resBlock, err := w.Client.Block(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get a checkpoint to verify from
|
||||
c, err := w.Commit(height)
|
||||
resCommit, err := w.Commit(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
check := certclient.CommitFromResult(c)
|
||||
sh := resCommit.SignedHeader
|
||||
|
||||
// now verify
|
||||
err = ValidateBlockMeta(r.BlockMeta, check)
|
||||
err = ValidateBlockMeta(resBlock.BlockMeta, sh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ValidateBlock(r.Block, check)
|
||||
err = ValidateBlock(resBlock.Block, sh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r, nil
|
||||
return resBlock, nil
|
||||
}
|
||||
|
||||
// Commit downloads the Commit and certifies it with the lite.
|
||||
//
|
||||
// This is the foundation for all other verification in this module
|
||||
func (w Wrapper) Commit(height *int64) (*ctypes.ResultCommit, error) {
|
||||
if height == nil {
|
||||
resStatus, err := w.Client.Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// NOTE: If resStatus.CatchingUp, there is a race
|
||||
// condition where the validator set for the next height
|
||||
// isn't available until some time after the blockstore
|
||||
// has height h on the remote node. This isn't an issue
|
||||
// once the node has caught up, and a syncing node likely
|
||||
// won't have this issue esp with the implementation we
|
||||
// have here, but we may have to address this at some
|
||||
// point.
|
||||
height = new(int64)
|
||||
*height = resStatus.SyncInfo.LatestBlockHeight
|
||||
}
|
||||
rpcclient.WaitForHeight(w.Client, *height, nil)
|
||||
r, err := w.Client.Commit(height)
|
||||
res, err := w.Client.Commit(height)
|
||||
// if we got it, then certify it
|
||||
if err == nil {
|
||||
check := certclient.CommitFromResult(r)
|
||||
err = w.cert.Certify(check)
|
||||
sh := res.SignedHeader
|
||||
err = w.cert.Certify(sh)
|
||||
}
|
||||
return r, err
|
||||
return res, err
|
||||
}
|
||||
|
||||
// // WrappedSwitch creates a websocket connection that auto-verifies any info
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
var _ Certifier = (*StaticCertifier)(nil)
|
||||
|
||||
// StaticCertifier assumes a static set of validators, set on
|
||||
// initilization and checks against them.
|
||||
// The signatures on every header is checked for > 2/3 votes
|
||||
// against the known validator set upon Certify
|
||||
//
|
||||
// Good for testing or really simple chains. Building block
|
||||
// to support real-world functionality.
|
||||
type StaticCertifier struct {
|
||||
chainID string
|
||||
vSet *types.ValidatorSet
|
||||
vhash []byte
|
||||
}
|
||||
|
||||
// NewStaticCertifier returns a new certifier with a static validator set.
|
||||
func NewStaticCertifier(chainID string, vals *types.ValidatorSet) *StaticCertifier {
|
||||
return &StaticCertifier{
|
||||
chainID: chainID,
|
||||
vSet: vals,
|
||||
}
|
||||
}
|
||||
|
||||
// ChainID returns the chain id.
|
||||
// Implements Certifier.
|
||||
func (sc *StaticCertifier) ChainID() string {
|
||||
return sc.chainID
|
||||
}
|
||||
|
||||
// Validators returns the validator set.
|
||||
func (sc *StaticCertifier) Validators() *types.ValidatorSet {
|
||||
return sc.vSet
|
||||
}
|
||||
|
||||
// Hash returns the hash of the validator set.
|
||||
func (sc *StaticCertifier) Hash() []byte {
|
||||
if len(sc.vhash) == 0 {
|
||||
sc.vhash = sc.vSet.Hash()
|
||||
}
|
||||
return sc.vhash
|
||||
}
|
||||
|
||||
// Certify makes sure that the commit is valid.
|
||||
// Implements Certifier.
|
||||
func (sc *StaticCertifier) Certify(commit Commit) error {
|
||||
// do basic sanity checks
|
||||
err := commit.ValidateBasic(sc.chainID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure it has the same validator set we have (static means static)
|
||||
if !bytes.Equal(sc.Hash(), commit.Header.ValidatorsHash) {
|
||||
return liteErr.ErrValidatorsChanged()
|
||||
}
|
||||
|
||||
// then make sure we have the proper signatures for this
|
||||
err = sc.vSet.VerifyCommit(sc.chainID, commit.Commit.BlockID,
|
||||
commit.Header.Height, commit.Commit)
|
||||
return errors.WithStack(err)
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package lite_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/tendermint/tendermint/lite"
|
||||
liteErr "github.com/tendermint/tendermint/lite/errors"
|
||||
)
|
||||
|
||||
func TestStaticCert(t *testing.T) {
|
||||
// assert, require := assert.New(t), require.New(t)
|
||||
assert := assert.New(t)
|
||||
// require := require.New(t)
|
||||
|
||||
keys := lite.GenValKeys(4)
|
||||
// 20, 30, 40, 50 - the first 3 don't have 2/3, the last 3 do!
|
||||
vals := keys.ToValidators(20, 10)
|
||||
// and a certifier based on our known set
|
||||
chainID := "test-static"
|
||||
cert := lite.NewStaticCertifier(chainID, vals)
|
||||
|
||||
cases := []struct {
|
||||
keys lite.ValKeys
|
||||
vals *types.ValidatorSet
|
||||
height int64
|
||||
first, last int // who actually signs
|
||||
proper bool // true -> expect no error
|
||||
changed bool // true -> expect validator change error
|
||||
}{
|
||||
// perfect, signed by everyone
|
||||
{keys, vals, 1, 0, len(keys), true, false},
|
||||
// skip little guy is okay
|
||||
{keys, vals, 2, 1, len(keys), true, false},
|
||||
// but not the big guy
|
||||
{keys, vals, 3, 0, len(keys) - 1, false, false},
|
||||
// even changing the power a little bit breaks the static validator
|
||||
// the sigs are enough, but the validator hash is unknown
|
||||
{keys, keys.ToValidators(20, 11), 4, 0, len(keys), false, true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
check := tc.keys.GenCommit(chainID, tc.height, nil, tc.vals,
|
||||
[]byte("foo"), []byte("params"), []byte("results"), tc.first, tc.last)
|
||||
err := cert.Certify(check)
|
||||
if tc.proper {
|
||||
assert.Nil(err, "%+v", err)
|
||||
} else {
|
||||
assert.NotNil(err)
|
||||
if tc.changed {
|
||||
assert.True(liteErr.IsValidatorsChangedErr(err), "%+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package lite
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// Verifier checks the votes to make sure the block really is signed properly.
|
||||
// Verifier must know the current or recent set of validitors by some other
|
||||
// means.
|
||||
type Verifier interface {
|
||||
Certify(sheader types.SignedHeader) error
|
||||
ChainID() string
|
||||
}
|
|
@ -370,16 +370,16 @@ func BlockResults(heightPtr *int64) (*ctypes.ResultBlockResults, error) {
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func getHeight(storeHeight int64, heightPtr *int64) (int64, error) {
|
||||
func getHeight(currentHeight int64, heightPtr *int64) (int64, error) {
|
||||
if heightPtr != nil {
|
||||
height := *heightPtr
|
||||
if height <= 0 {
|
||||
return 0, fmt.Errorf("Height must be greater than 0")
|
||||
}
|
||||
if height > storeHeight {
|
||||
if height > currentHeight {
|
||||
return 0, fmt.Errorf("Height must be less than or equal to the current blockchain height")
|
||||
}
|
||||
return height, nil
|
||||
}
|
||||
return storeHeight, nil
|
||||
return currentHeight, nil
|
||||
}
|
||||
|
|
|
@ -44,8 +44,10 @@ import (
|
|||
// }
|
||||
// ```
|
||||
func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) {
|
||||
storeHeight := blockStore.Height()
|
||||
height, err := getHeight(storeHeight, heightPtr)
|
||||
// The latest validator that we know is the
|
||||
// NextValidator of the last block.
|
||||
height := consensusState.GetState().LastBlockHeight + 1
|
||||
height, err := getHeight(height, heightPtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ var subscribeTimeout = 5 * time.Second
|
|||
type Consensus interface {
|
||||
GetState() sm.State
|
||||
GetValidators() (int64, []*types.Validator)
|
||||
GetLastHeight() int64
|
||||
GetRoundStateJSON() ([]byte, error)
|
||||
GetRoundStateSimpleJSON() ([]byte, error)
|
||||
}
|
||||
|
|
|
@ -64,7 +64,12 @@ import (
|
|||
//}
|
||||
// ```
|
||||
func Status() (*ctypes.ResultStatus, error) {
|
||||
latestHeight := blockStore.Height()
|
||||
var latestHeight int64 = -1
|
||||
if consensusReactor.FastSync() {
|
||||
latestHeight = blockStore.Height()
|
||||
} else {
|
||||
latestHeight = consensusState.GetLastHeight()
|
||||
}
|
||||
var (
|
||||
latestBlockMeta *types.BlockMeta
|
||||
latestBlockHash cmn.HexBytes
|
||||
|
@ -107,9 +112,8 @@ func Status() (*ctypes.ResultStatus, error) {
|
|||
func validatorAtHeight(h int64) *types.Validator {
|
||||
privValAddress := pubKey.Address()
|
||||
|
||||
// If we're still at height h, search in the current validator set.
|
||||
lastBlockHeight, vals := consensusState.GetValidators()
|
||||
|
||||
// if we're still at height h, search in the current validator set
|
||||
if lastBlockHeight == h {
|
||||
for _, val := range vals {
|
||||
if bytes.Equal(val.Address, privValAddress) {
|
||||
|
@ -118,12 +122,11 @@ func validatorAtHeight(h int64) *types.Validator {
|
|||
}
|
||||
}
|
||||
|
||||
// if we've moved to the next height, retrieve the validator set from DB
|
||||
// If we've moved to the next height, retrieve the validator set from DB.
|
||||
if lastBlockHeight > h {
|
||||
vals, err := sm.LoadValidators(stateDB, h)
|
||||
if err != nil {
|
||||
// should not happen
|
||||
return nil
|
||||
return nil // should not happen
|
||||
}
|
||||
_, val := vals.GetByAddress(privValAddress)
|
||||
return val
|
||||
|
|
|
@ -33,10 +33,8 @@ type ResultBlock struct {
|
|||
|
||||
// Commit and Header
|
||||
type ResultCommit struct {
|
||||
// SignedHeader is header and commit, embedded so we only have
|
||||
// one level in the json output
|
||||
types.SignedHeader
|
||||
CanonicalCommit bool `json:"canonical"`
|
||||
types.SignedHeader `json:"signed_header"`
|
||||
CanonicalCommit bool `json:"canonical"`
|
||||
}
|
||||
|
||||
// ABCI results from a block
|
||||
|
|
|
@ -61,7 +61,7 @@ func StartHTTPServer(
|
|||
listener,
|
||||
RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger),
|
||||
)
|
||||
logger.Error("RPC HTTP server stopped", "err", err)
|
||||
logger.Info("RPC HTTP server stopped", "err", err)
|
||||
}()
|
||||
return listener, nil
|
||||
}
|
||||
|
|
|
@ -80,18 +80,18 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b
|
|||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// save the results before we commit
|
||||
// Save the results before we commit.
|
||||
saveABCIResponses(blockExec.db, block.Height, abciResponses)
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// update the state with the block and responses
|
||||
// Update the state with the block and responses.
|
||||
state, err = updateState(state, blockID, &block.Header, abciResponses)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("Commit failed for application: %v", err)
|
||||
}
|
||||
|
||||
// lock mempool, commit app state, update mempoool
|
||||
// Lock mempool, commit app state, update mempoool.
|
||||
appHash, err := blockExec.Commit(block)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("Commit failed for application: %v", err)
|
||||
|
@ -102,13 +102,13 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b
|
|||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// update the app hash and save the state
|
||||
// Update the app hash and save the state.
|
||||
state.AppHash = appHash
|
||||
SaveState(blockExec.db, state)
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// events are fired after everything else
|
||||
// Events are fired after everything else.
|
||||
// NOTE: if we crash between Commit and Save, events wont be fired during replay
|
||||
fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses)
|
||||
|
||||
|
@ -164,7 +164,7 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
|
|||
txIndex := 0
|
||||
abciResponses := NewABCIResponses(block)
|
||||
|
||||
// Execute transactions and get hash
|
||||
// Execute transactions and get hash.
|
||||
proxyCb := func(req *abci.Request, res *abci.Response) {
|
||||
switch r := res.Value.(type) {
|
||||
case *abci.Response_DeliverTx:
|
||||
|
@ -186,7 +186,7 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
|
|||
|
||||
signVals, byzVals := getBeginBlockValidatorInfo(block, lastValSet, stateDB)
|
||||
|
||||
// Begin block
|
||||
// Begin block.
|
||||
_, err := proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{
|
||||
Hash: block.Hash(),
|
||||
Header: types.TM2PB.Header(&block.Header),
|
||||
|
@ -201,7 +201,7 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Run txs of block
|
||||
// Run txs of block.
|
||||
for _, tx := range block.Txs {
|
||||
proxyAppConn.DeliverTxAsync(tx)
|
||||
if err := proxyAppConn.Error(); err != nil {
|
||||
|
@ -209,7 +209,7 @@ func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus,
|
|||
}
|
||||
}
|
||||
|
||||
// End block
|
||||
// End block.
|
||||
abciResponses.EndBlock, err = proxyAppConn.EndBlockSync(abci.RequestEndBlock{Height: block.Height})
|
||||
if err != nil {
|
||||
logger.Error("Error in proxyAppConn.EndBlock", "err", err)
|
||||
|
@ -314,26 +314,25 @@ func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.Validat
|
|||
func updateState(state State, blockID types.BlockID, header *types.Header,
|
||||
abciResponses *ABCIResponses) (State, error) {
|
||||
|
||||
// copy the valset so we can apply changes from EndBlock
|
||||
// and update s.LastValidators and s.Validators
|
||||
prevValSet := state.Validators.Copy()
|
||||
nextValSet := prevValSet.Copy()
|
||||
// Copy the valset so we can apply changes from EndBlock
|
||||
// and update s.LastValidators and s.Validators.
|
||||
nValSet := state.NextValidators.Copy()
|
||||
|
||||
// update the validator set with the latest abciResponses
|
||||
// Update the validator set with the latest abciResponses.
|
||||
lastHeightValsChanged := state.LastHeightValidatorsChanged
|
||||
if len(abciResponses.EndBlock.ValidatorUpdates) > 0 {
|
||||
err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates)
|
||||
err := updateValidators(nValSet, abciResponses.EndBlock.ValidatorUpdates)
|
||||
if err != nil {
|
||||
return state, fmt.Errorf("Error changing validator set: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
lastHeightValsChanged = header.Height + 1
|
||||
// Change results from this height but only applies to the next next height.
|
||||
lastHeightValsChanged = header.Height + 1 + 1
|
||||
}
|
||||
|
||||
// Update validator accums and set state variables
|
||||
nextValSet.IncrementAccum(1)
|
||||
// Update validator accums and set state variables.
|
||||
nValSet.IncrementAccum(1)
|
||||
|
||||
// update the params with the latest abciResponses
|
||||
// Update the params with the latest abciResponses.
|
||||
nextParams := state.ConsensusParams
|
||||
lastHeightParamsChanged := state.LastHeightConsensusParamsChanged
|
||||
if abciResponses.EndBlock.ConsensusParamUpdates != nil {
|
||||
|
@ -343,7 +342,7 @@ func updateState(state State, blockID types.BlockID, header *types.Header,
|
|||
if err != nil {
|
||||
return state, fmt.Errorf("Error updating consensus params: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
// Change results from this height but only applies to the next height.
|
||||
lastHeightParamsChanged = header.Height + 1
|
||||
}
|
||||
|
||||
|
@ -355,7 +354,8 @@ func updateState(state State, blockID types.BlockID, header *types.Header,
|
|||
LastBlockTotalTx: state.LastBlockTotalTx + header.NumTxs,
|
||||
LastBlockID: blockID,
|
||||
LastBlockTime: header.Time,
|
||||
Validators: nextValSet,
|
||||
NextValidators: nValSet,
|
||||
Validators: state.NextValidators.Copy(),
|
||||
LastValidators: state.Validators.Copy(),
|
||||
LastHeightValidatorsChanged: lastHeightValsChanged,
|
||||
ConsensusParams: nextParams,
|
||||
|
|
|
@ -24,7 +24,7 @@ var (
|
|||
// Instead, use state.Copy() or state.NextState(...).
|
||||
// NOTE: not goroutine-safe.
|
||||
type State struct {
|
||||
// Immutable
|
||||
// immutable
|
||||
ChainID string
|
||||
|
||||
// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
|
||||
|
@ -38,6 +38,7 @@ type State struct {
|
|||
// so we can query for historical validator sets.
|
||||
// Note that if s.LastBlockHeight causes a valset change,
|
||||
// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1
|
||||
NextValidators *types.ValidatorSet
|
||||
Validators *types.ValidatorSet
|
||||
LastValidators *types.ValidatorSet
|
||||
LastHeightValidatorsChanged int64
|
||||
|
@ -50,7 +51,7 @@ type State struct {
|
|||
// Merkle root of the results from executing prev block
|
||||
LastResultsHash []byte
|
||||
|
||||
// The latest AppHash we've received from calling abci.Commit()
|
||||
// the latest AppHash we've received from calling abci.Commit()
|
||||
AppHash []byte
|
||||
}
|
||||
|
||||
|
@ -64,6 +65,7 @@ func (state State) Copy() State {
|
|||
LastBlockID: state.LastBlockID,
|
||||
LastBlockTime: state.LastBlockTime,
|
||||
|
||||
NextValidators: state.NextValidators.Copy(),
|
||||
Validators: state.Validators.Copy(),
|
||||
LastValidators: state.LastValidators.Copy(),
|
||||
LastHeightValidatorsChanged: state.LastHeightValidatorsChanged,
|
||||
|
@ -93,11 +95,6 @@ func (state State) IsEmpty() bool {
|
|||
return state.Validators == nil // XXX can't compare to Empty
|
||||
}
|
||||
|
||||
// GetValidators returns the last and current validator sets.
|
||||
func (state State) GetValidators() (last *types.ValidatorSet, current *types.ValidatorSet) {
|
||||
return state.LastValidators, state.Validators
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Create a block from the latest state
|
||||
|
||||
|
@ -119,6 +116,7 @@ func (state State) MakeBlock(
|
|||
block.TotalTxs = state.LastBlockTotalTx + block.NumTxs
|
||||
|
||||
block.ValidatorsHash = state.Validators.Hash()
|
||||
block.NextValidatorsHash = state.NextValidators.Hash()
|
||||
block.ConsensusHash = state.ConsensusParams.Hash()
|
||||
block.AppHash = state.AppHash
|
||||
block.LastResultsHash = state.LastResultsHash
|
||||
|
@ -183,6 +181,7 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
|
|||
LastBlockID: types.BlockID{},
|
||||
LastBlockTime: genDoc.GenesisTime,
|
||||
|
||||
NextValidators: types.NewValidatorSet(validators).CopyIncrementAccum(1),
|
||||
Validators: types.NewValidatorSet(validators),
|
||||
LastValidators: types.NewValidatorSet(nil),
|
||||
LastHeightValidatorsChanged: 1,
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// setupTestCase does setup common to all test cases
|
||||
// setupTestCase does setup common to all test cases.
|
||||
func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, State) {
|
||||
config := cfg.ResetTestRoot("state_")
|
||||
dbType := dbm.DBBackendType(config.DBBackend)
|
||||
|
@ -73,7 +73,7 @@ func TestABCIResponsesSaveLoad1(t *testing.T) {
|
|||
|
||||
state.LastBlockHeight++
|
||||
|
||||
// build mock responses
|
||||
// Build mock responses.
|
||||
block := makeBlock(state, 2)
|
||||
abciResponses := NewABCIResponses(block)
|
||||
abciResponses.DeliverTx[0] = &abci.ResponseDeliverTx{Data: []byte("foo"), Tags: nil}
|
||||
|
@ -90,7 +90,7 @@ func TestABCIResponsesSaveLoad1(t *testing.T) {
|
|||
loadedABCIResponses, abciResponses))
|
||||
}
|
||||
|
||||
// TestResultsSaveLoad tests saving and loading abci results.
|
||||
// TestResultsSaveLoad tests saving and loading ABCI results.
|
||||
func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
tearDown, stateDB, _ := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
|
@ -98,8 +98,8 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
cases := [...]struct {
|
||||
// height is implied index+2
|
||||
// as block 1 is created from genesis
|
||||
// Height is implied to equal index+2,
|
||||
// as block 1 is created from genesis.
|
||||
added []*abci.ResponseDeliverTx
|
||||
expected types.ABCIResults
|
||||
}{
|
||||
|
@ -133,14 +133,14 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
// query all before, should return error
|
||||
// Query all before, this should return error.
|
||||
for i := range cases {
|
||||
h := int64(i + 1)
|
||||
res, err := LoadABCIResponses(stateDB, h)
|
||||
assert.Error(err, "%d: %#v", i, res)
|
||||
}
|
||||
|
||||
// add all cases
|
||||
// Add all cases.
|
||||
for i, tc := range cases {
|
||||
h := int64(i + 1) // last block height, one below what we save
|
||||
responses := &ABCIResponses{
|
||||
|
@ -150,7 +150,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
|||
saveABCIResponses(stateDB, h, responses)
|
||||
}
|
||||
|
||||
// query all before, should return expected value
|
||||
// Query all before, should return expected value.
|
||||
for i, tc := range cases {
|
||||
h := int64(i + 1)
|
||||
res, err := LoadABCIResponses(stateDB, h)
|
||||
|
@ -166,34 +166,30 @@ func TestValidatorSimpleSaveLoad(t *testing.T) {
|
|||
// nolint: vetshadow
|
||||
assert := assert.New(t)
|
||||
|
||||
// can't load anything for height 0
|
||||
// Can't load anything for height 0.
|
||||
v, err := LoadValidators(stateDB, 0)
|
||||
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0")
|
||||
|
||||
// should be able to load for height 1
|
||||
// Should be able to load for height 1.
|
||||
v, err = LoadValidators(stateDB, 1)
|
||||
assert.Nil(err, "expected no err at height 1")
|
||||
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// increment height, save; should be able to load for next height
|
||||
// Should be able to load for height 2.
|
||||
v, err = LoadValidators(stateDB, 2)
|
||||
assert.Nil(err, "expected no err at height 2")
|
||||
assert.Equal(v.Hash(), state.NextValidators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// Increment height, save; should be able to load for next & next next height.
|
||||
state.LastBlockHeight++
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
v, err = LoadValidators(stateDB, nextHeight)
|
||||
saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
|
||||
vp0, err := LoadValidators(stateDB, nextHeight+0)
|
||||
assert.Nil(err, "expected no err")
|
||||
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// increment height, save; should be able to load for next height
|
||||
state.LastBlockHeight += 10
|
||||
nextHeight = state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
v, err = LoadValidators(stateDB, nextHeight)
|
||||
vp1, err := LoadValidators(stateDB, nextHeight+1)
|
||||
assert.Nil(err, "expected no err")
|
||||
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// should be able to load for next next height
|
||||
_, err = LoadValidators(stateDB, state.LastBlockHeight+2)
|
||||
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height")
|
||||
assert.Equal(vp0.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
assert.Equal(vp1.Hash(), state.NextValidators.Hash(), "expected next validator hashes to match")
|
||||
}
|
||||
|
||||
// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes.
|
||||
|
@ -201,20 +197,19 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
tearDown, stateDB, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
|
||||
// change vals at these heights
|
||||
// Change vals at these heights.
|
||||
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
||||
N := len(changeHeights)
|
||||
|
||||
// build the validator history by running updateState
|
||||
// with the right validator set for each height
|
||||
// Build the validator history by running updateState
|
||||
// with the right validator set for each height.
|
||||
highestHeight := changeHeights[N-1] + 5
|
||||
changeIndex := 0
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
power := val.VotingPower
|
||||
var err error
|
||||
for i := int64(1); i < highestHeight; i++ {
|
||||
// when we get to a change height,
|
||||
// use the next pubkey
|
||||
// When we get to a change height, use the next pubkey.
|
||||
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
|
||||
changeIndex++
|
||||
power++
|
||||
|
@ -223,16 +218,16 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
state, err = updateState(state, blockID, &header, responses)
|
||||
assert.Nil(t, err)
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
|
||||
}
|
||||
|
||||
// on each change height, increment the power by one.
|
||||
// On each height change, increment the power by one.
|
||||
testCases := make([]int64, highestHeight)
|
||||
changeIndex = 0
|
||||
power = val.VotingPower
|
||||
for i := int64(1); i < highestHeight+1; i++ {
|
||||
// we we get to the height after a change height
|
||||
// use the next pubkey (note our counter starts at 0 this time)
|
||||
// We get to the height after a change height use the next pubkey (note
|
||||
// our counter starts at 0 this time).
|
||||
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
|
||||
changeIndex++
|
||||
power++
|
||||
|
@ -241,7 +236,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, power := range testCases {
|
||||
v, err := LoadValidators(stateDB, int64(i+1))
|
||||
v, err := LoadValidators(stateDB, int64(i+1+1)) // +1 because vset changes delayed by 1 block.
|
||||
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i))
|
||||
assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
|
||||
_, val := v.GetByIndex(0)
|
||||
|
@ -256,25 +251,42 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
func TestManyValidatorChangesSaveLoad(t *testing.T) {
|
||||
const valSetSize = 7
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
require.Equal(t, int64(0), state.LastBlockHeight)
|
||||
state.Validators = genValSet(valSetSize)
|
||||
state.NextValidators = state.Validators.CopyIncrementAccum(1)
|
||||
SaveState(stateDB, state)
|
||||
defer tearDown(t)
|
||||
|
||||
const height = 1
|
||||
_, valOld := state.Validators.GetByIndex(0)
|
||||
var pubkeyOld = valOld.PubKey
|
||||
pubkey := ed25519.GenPrivKey().PubKey()
|
||||
// swap the first validator with a new one ^^^ (validator set size stays the same)
|
||||
const height = 1
|
||||
|
||||
// Swap the first validator with a new one (validator set size stays the same).
|
||||
header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey)
|
||||
|
||||
// Save state etc.
|
||||
var err error
|
||||
state, err = updateState(state, blockID, &header, responses)
|
||||
require.Nil(t, err)
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
|
||||
|
||||
v, err := LoadValidators(stateDB, height+1)
|
||||
// Load nextheight, it should be the oldpubkey.
|
||||
v0, err := LoadValidators(stateDB, nextHeight)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, valSetSize, v.Size())
|
||||
assert.Equal(t, valSetSize, v0.Size())
|
||||
index, val := v0.GetByAddress(pubkeyOld.Address())
|
||||
assert.NotNil(t, val)
|
||||
if index < 0 {
|
||||
t.Fatal("expected to find old validator")
|
||||
}
|
||||
|
||||
index, val := v.GetByAddress(pubkey.Address())
|
||||
// Load nextheight+1, it should be the new pubkey.
|
||||
v1, err := LoadValidators(stateDB, nextHeight+1)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, valSetSize, v1.Size())
|
||||
index, val = v1.GetByAddress(pubkey.Address())
|
||||
assert.NotNil(t, val)
|
||||
if index < 0 {
|
||||
t.Fatal("expected to find newly added validator")
|
||||
|
@ -295,12 +307,12 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
tearDown, stateDB, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
|
||||
// change vals at these heights
|
||||
// Change vals at these heights.
|
||||
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
||||
N := len(changeHeights)
|
||||
|
||||
// each valset is just one validator
|
||||
// create list of them
|
||||
// Each valset is just one validator.
|
||||
// create list of them.
|
||||
params := make([]types.ConsensusParams, N+1)
|
||||
params[0] = state.ConsensusParams
|
||||
for i := 1; i < N+1; i++ {
|
||||
|
@ -308,15 +320,14 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
params[i].BlockSize.MaxBytes += i
|
||||
}
|
||||
|
||||
// build the params history by running updateState
|
||||
// with the right params set for each height
|
||||
// Build the params history by running updateState
|
||||
// with the right params set for each height.
|
||||
highestHeight := changeHeights[N-1] + 5
|
||||
changeIndex := 0
|
||||
cp := params[changeIndex]
|
||||
var err error
|
||||
for i := int64(1); i < highestHeight; i++ {
|
||||
// when we get to a change height,
|
||||
// use the next params
|
||||
// When we get to a change height, use the next params.
|
||||
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] {
|
||||
changeIndex++
|
||||
cp = params[changeIndex]
|
||||
|
@ -329,13 +340,13 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
saveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
|
||||
}
|
||||
|
||||
// make all the test cases by using the same params until after the change
|
||||
// Make all the test cases by using the same params until after the change.
|
||||
testCases := make([]paramsChangeTestCase, highestHeight)
|
||||
changeIndex = 0
|
||||
cp = params[changeIndex]
|
||||
for i := int64(1); i < highestHeight+1; i++ {
|
||||
// we we get to the height after a change height
|
||||
// use the next pubkey (note our counter starts at 0 this time)
|
||||
// We get to the height after a change height use the next pubkey (note
|
||||
// our counter starts at 0 this time).
|
||||
if changeIndex < len(changeHeights) && i == changeHeights[changeIndex]+1 {
|
||||
changeIndex++
|
||||
cp = params[changeIndex]
|
||||
|
@ -423,13 +434,13 @@ func TestApplyUpdates(t *testing.T) {
|
|||
func makeHeaderPartsResponsesValPubKeyChange(state State, height int64,
|
||||
pubkey crypto.PubKey) (types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
block := makeBlock(state, state.LastBlockHeight+1)
|
||||
abciResponses := &ABCIResponses{
|
||||
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
|
||||
}
|
||||
|
||||
// if the pubkey is new, remove the old and add the new
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
// If the pubkey is new, remove the old and add the new.
|
||||
_, val := state.NextValidators.GetByIndex(0)
|
||||
if !bytes.Equal(pubkey.Bytes(), val.PubKey.Bytes()) {
|
||||
abciResponses.EndBlock = &abci.ResponseEndBlock{
|
||||
ValidatorUpdates: []abci.Validator{
|
||||
|
@ -445,13 +456,13 @@ func makeHeaderPartsResponsesValPubKeyChange(state State, height int64,
|
|||
func makeHeaderPartsResponsesValPowerChange(state State, height int64,
|
||||
power int64) (types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
block := makeBlock(state, state.LastBlockHeight+1)
|
||||
abciResponses := &ABCIResponses{
|
||||
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil},
|
||||
}
|
||||
|
||||
// if the pubkey is new, remove the old and add the new
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
// If the pubkey is new, remove the old and add the new.
|
||||
_, val := state.NextValidators.GetByIndex(0)
|
||||
if val.VotingPower != power {
|
||||
abciResponses.EndBlock = &abci.ResponseEndBlock{
|
||||
ValidatorUpdates: []abci.Validator{
|
||||
|
@ -466,7 +477,7 @@ func makeHeaderPartsResponsesValPowerChange(state State, height int64,
|
|||
func makeHeaderPartsResponsesParams(state State, height int64,
|
||||
params types.ConsensusParams) (types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
block := makeBlock(state, state.LastBlockHeight+1)
|
||||
abciResponses := &ABCIResponses{
|
||||
EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)},
|
||||
}
|
||||
|
|
|
@ -87,7 +87,14 @@ func SaveState(db dbm.DB, state State) {
|
|||
|
||||
func saveState(db dbm.DB, state State, key []byte) {
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(db, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
// If first block, save validators for block 1.
|
||||
if nextHeight == 1 {
|
||||
lastHeightVoteChanged := int64(1) // Due to Tendermint validator set changes being delayed 1 block.
|
||||
saveValidatorsInfo(db, nextHeight, lastHeightVoteChanged, state.Validators)
|
||||
}
|
||||
// Save next validators.
|
||||
saveValidatorsInfo(db, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators)
|
||||
// Save next consensus params.
|
||||
saveConsensusParamsInfo(db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
|
||||
db.SetSync(stateKey, state.Bytes())
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ import (
|
|||
// Validate block
|
||||
|
||||
func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
|
||||
// validate internal consistency
|
||||
// Validate internal consistency.
|
||||
if err := block.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate basic info
|
||||
// Validate basic info.
|
||||
if block.ChainID != state.ChainID {
|
||||
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", state.ChainID, block.ChainID)
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
|
|||
}
|
||||
*/
|
||||
|
||||
// validate prev block info
|
||||
// Validate prev block info.
|
||||
if !block.LastBlockID.Equals(state.LastBlockID) {
|
||||
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", state.LastBlockID, block.LastBlockID)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
|
|||
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", state.LastBlockTotalTx+newTxs, block.TotalTxs)
|
||||
}
|
||||
|
||||
// validate app info
|
||||
// Validate app info
|
||||
if !bytes.Equal(block.AppHash, state.AppHash) {
|
||||
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", state.AppHash, block.AppHash)
|
||||
}
|
||||
|
@ -55,6 +55,9 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
|
|||
if !bytes.Equal(block.ValidatorsHash, state.Validators.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", state.Validators.Hash(), block.ValidatorsHash)
|
||||
}
|
||||
if !bytes.Equal(block.NextValidatorsHash, state.NextValidators.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.NextValidatorsHash. Expected %X, got %v", state.NextValidators.Hash(), block.NextValidatorsHash)
|
||||
}
|
||||
|
||||
// Validate block LastCommit.
|
||||
if block.Height == 1 {
|
||||
|
@ -73,6 +76,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Validate all evidence.
|
||||
// TODO: Each check requires loading an old validator set.
|
||||
// We should cap the amount of evidence per block
|
||||
// to prevent potential proposer DoS.
|
||||
|
|
|
@ -2,12 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/tendermint/go-amino"
|
||||
"github.com/tendermint/tendermint/rpc/grpc"
|
||||
)
|
||||
|
||||
|
@ -33,7 +33,7 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
bz, err := json.Marshal(res)
|
||||
bz, err := amino.NewCodec().MarshalJSON(res)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
|
|
@ -21,8 +21,15 @@ COPY . $REPO
|
|||
RUN make get_tools
|
||||
RUN make get_vendor_deps
|
||||
|
||||
RUN go install ./cmd/tendermint
|
||||
RUN go install ./abci/cmd/abci-cli
|
||||
# Now copy in the code
|
||||
# NOTE: this will overwrite whatever is in vendor/
|
||||
COPY . $REPO
|
||||
|
||||
# install ABCI CLI
|
||||
RUN make install_abci
|
||||
|
||||
# install Tendermint
|
||||
RUN make install
|
||||
|
||||
# expose the volume for debugging
|
||||
VOLUME $REPO
|
||||
|
|
|
@ -56,6 +56,7 @@ for i in `seq 1 $N`; do
|
|||
# - assert block height is greater than 1
|
||||
BLOCK_HEIGHT=`curl -s $addr/status | jq .result.sync_info.latest_block_height | jq fromjson`
|
||||
COUNT=0
|
||||
echo "$$BLOCK_HEIGHT IS $BLOCK_HEIGHT"
|
||||
while [ "$BLOCK_HEIGHT" -le 1 ]; do
|
||||
echo "Waiting for node $i to commit a block ..."
|
||||
sleep 1
|
||||
|
|
|
@ -23,7 +23,7 @@ set -e
|
|||
|
||||
# get the first peer's height
|
||||
addr=$(test/p2p/ip.sh 1):26657
|
||||
h1=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height)
|
||||
h1=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height | sed -e "s/^\"\(.*\)\"$/\1/g")
|
||||
echo "1st peer is on height $h1"
|
||||
|
||||
echo "Waiting until other peers reporting a height higher than the 1st one"
|
||||
|
@ -33,7 +33,7 @@ for i in $(seq 2 "$NUM_OF_PEERS"); do
|
|||
|
||||
while [[ $hi -le $h1 ]] ; do
|
||||
addr=$(test/p2p/ip.sh "$i"):26657
|
||||
hi=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height)
|
||||
hi=$(curl -s "$addr/status" | jq .result.sync_info.latest_block_height | sed -e "s/^\"\(.*\)\"$/\1/g")
|
||||
|
||||
echo "... peer $i is on height $hi"
|
||||
|
||||
|
|
|
@ -14,14 +14,31 @@ echo "starting tendermint peer ID=$ID"
|
|||
# start tendermint container on the network
|
||||
# NOTE: $NODE_FLAGS should be unescaped (no quotes). otherwise it will be
|
||||
# treated as one flag.
|
||||
docker run -d \
|
||||
--net="$NETWORK_NAME" \
|
||||
--ip=$(test/p2p/ip.sh "$ID") \
|
||||
--name "local_testnet_$ID" \
|
||||
--entrypoint tendermint \
|
||||
-e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$ID/core" \
|
||||
--log-driver=syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:5514 \
|
||||
--log-opt syslog-facility=daemon \
|
||||
--log-opt tag="{{.Name}}" \
|
||||
"$DOCKER_IMAGE" node $NODE_FLAGS --log_level=debug --proxy_app="$APP_PROXY"
|
||||
|
||||
if [[ "$ID" == "x" ]]; then # Set "x" to "1" to print to console.
|
||||
docker run \
|
||||
--net="$NETWORK_NAME" \
|
||||
--ip=$(test/p2p/ip.sh "$ID") \
|
||||
--name "local_testnet_$ID" \
|
||||
--entrypoint tendermint \
|
||||
-e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$ID/core" \
|
||||
-e GOMAXPROCS=1 \
|
||||
--log-driver=syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:5514 \
|
||||
--log-opt syslog-facility=daemon \
|
||||
--log-opt tag="{{.Name}}" \
|
||||
"$DOCKER_IMAGE" node $NODE_FLAGS --log_level=debug --proxy_app="$APP_PROXY" &
|
||||
else
|
||||
docker run -d \
|
||||
--net="$NETWORK_NAME" \
|
||||
--ip=$(test/p2p/ip.sh "$ID") \
|
||||
--name "local_testnet_$ID" \
|
||||
--entrypoint tendermint \
|
||||
-e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$ID/core" \
|
||||
-e GOMAXPROCS=1 \
|
||||
--log-driver=syslog \
|
||||
--log-opt syslog-address=udp://127.0.0.1:5514 \
|
||||
--log-opt syslog-facility=daemon \
|
||||
--log-opt tag="{{.Name}}" \
|
||||
"$DOCKER_IMAGE" node $NODE_FLAGS --log_level=debug --proxy_app="$APP_PROXY"
|
||||
fi
|
||||
|
|
|
@ -16,6 +16,7 @@ CLIENT_NAME="pex_addrbook_$ID"
|
|||
|
||||
echo "1. restart peer $ID"
|
||||
docker stop "local_testnet_$ID"
|
||||
echo "stopped local_testnet_$ID"
|
||||
# preserve addrbook.json
|
||||
docker cp "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json" "/tmp/addrbook.json"
|
||||
set +e #CIRCLE
|
||||
|
@ -24,6 +25,13 @@ set -e
|
|||
|
||||
# NOTE that we do not provide persistent_peers
|
||||
bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe"
|
||||
echo "started local_testnet_$ID"
|
||||
|
||||
# if the client runs forever, it means addrbook wasn't saved or was empty
|
||||
bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N"
|
||||
|
||||
# Now we know that the node is up.
|
||||
|
||||
docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json"
|
||||
echo "with the following addrbook:"
|
||||
cat /tmp/addrbook.json
|
||||
|
@ -31,9 +39,6 @@ cat /tmp/addrbook.json
|
|||
# docker exec "local_testnet_$ID" cat "/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json"
|
||||
echo ""
|
||||
|
||||
# if the client runs forever, it means addrbook wasn't saved or was empty
|
||||
bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N"
|
||||
|
||||
echo "----------------------------------------------------------------------"
|
||||
echo "Testing other peers connect to us if we have neither persistent_peers nor the addrbook"
|
||||
echo "(assuming peers are started with pex enabled)"
|
||||
|
@ -42,16 +47,20 @@ CLIENT_NAME="pex_no_addrbook_$ID"
|
|||
|
||||
echo "1. restart peer $ID"
|
||||
docker stop "local_testnet_$ID"
|
||||
echo "stopped local_testnet_$ID"
|
||||
set +e #CIRCLE
|
||||
docker rm -vf "local_testnet_$ID"
|
||||
set -e
|
||||
|
||||
# NOTE that we do not provide persistent_peers
|
||||
bash test/p2p/peer.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$ID" "$PROXY_APP" "--p2p.pex --rpc.unsafe"
|
||||
echo "started local_testnet_$ID"
|
||||
|
||||
# if the client runs forever, it means other peers have removed us from their books (which should not happen)
|
||||
bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p/pex/check_peer.sh $ID $N"
|
||||
|
||||
# Now we know that the node is up.
|
||||
|
||||
echo ""
|
||||
echo "PASS"
|
||||
echo ""
|
||||
|
|
115
types/block.go
115
types/block.go
|
@ -193,10 +193,11 @@ type Header struct {
|
|||
DataHash cmn.HexBytes `json:"data_hash"` // transactions
|
||||
|
||||
// hashes from the app output from the prev block
|
||||
ValidatorsHash cmn.HexBytes `json:"validators_hash"` // validators for the current block
|
||||
ConsensusHash cmn.HexBytes `json:"consensus_hash"` // consensus params for current block
|
||||
AppHash cmn.HexBytes `json:"app_hash"` // state after txs from the previous block
|
||||
LastResultsHash cmn.HexBytes `json:"last_results_hash"` // root hash of all results from the txs from the previous block
|
||||
ValidatorsHash cmn.HexBytes `json:"validators_hash"` // validators for the current block
|
||||
NextValidatorsHash cmn.HexBytes `json:"next_validators_hash"` // validators for the next block
|
||||
ConsensusHash cmn.HexBytes `json:"consensus_hash"` // consensus params for current block
|
||||
AppHash cmn.HexBytes `json:"app_hash"` // state after txs from the previous block
|
||||
LastResultsHash cmn.HexBytes `json:"last_results_hash"` // root hash of all results from the txs from the previous block
|
||||
|
||||
// consensus info
|
||||
EvidenceHash cmn.HexBytes `json:"evidence_hash"` // evidence included in the block
|
||||
|
@ -211,19 +212,20 @@ func (h *Header) Hash() cmn.HexBytes {
|
|||
return nil
|
||||
}
|
||||
return merkle.SimpleHashFromMap(map[string]merkle.Hasher{
|
||||
"ChainID": aminoHasher(h.ChainID),
|
||||
"Height": aminoHasher(h.Height),
|
||||
"Time": aminoHasher(h.Time),
|
||||
"NumTxs": aminoHasher(h.NumTxs),
|
||||
"TotalTxs": aminoHasher(h.TotalTxs),
|
||||
"LastBlockID": aminoHasher(h.LastBlockID),
|
||||
"LastCommit": aminoHasher(h.LastCommitHash),
|
||||
"Data": aminoHasher(h.DataHash),
|
||||
"Validators": aminoHasher(h.ValidatorsHash),
|
||||
"App": aminoHasher(h.AppHash),
|
||||
"Consensus": aminoHasher(h.ConsensusHash),
|
||||
"Results": aminoHasher(h.LastResultsHash),
|
||||
"Evidence": aminoHasher(h.EvidenceHash),
|
||||
"ChainID": aminoHasher(h.ChainID),
|
||||
"Height": aminoHasher(h.Height),
|
||||
"Time": aminoHasher(h.Time),
|
||||
"NumTxs": aminoHasher(h.NumTxs),
|
||||
"TotalTxs": aminoHasher(h.TotalTxs),
|
||||
"LastBlockID": aminoHasher(h.LastBlockID),
|
||||
"LastCommit": aminoHasher(h.LastCommitHash),
|
||||
"Data": aminoHasher(h.DataHash),
|
||||
"Validators": aminoHasher(h.ValidatorsHash),
|
||||
"NextValidators": aminoHasher(h.NextValidatorsHash),
|
||||
"App": aminoHasher(h.AppHash),
|
||||
"Consensus": aminoHasher(h.ConsensusHash),
|
||||
"Results": aminoHasher(h.LastResultsHash),
|
||||
"Evidence": aminoHasher(h.EvidenceHash),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -242,6 +244,7 @@ func (h *Header) StringIndented(indent string) string {
|
|||
%s LastCommit: %v
|
||||
%s Data: %v
|
||||
%s Validators: %v
|
||||
%s NextValidators: %v
|
||||
%s App: %v
|
||||
%s Consensus: %v
|
||||
%s Results: %v
|
||||
|
@ -256,6 +259,7 @@ func (h *Header) StringIndented(indent string) string {
|
|||
indent, h.LastCommitHash,
|
||||
indent, h.DataHash,
|
||||
indent, h.ValidatorsHash,
|
||||
indent, h.NextValidatorsHash,
|
||||
indent, h.AppHash,
|
||||
indent, h.ConsensusHash,
|
||||
indent, h.LastResultsHash,
|
||||
|
@ -353,6 +357,7 @@ func (commit *Commit) IsCommit() bool {
|
|||
}
|
||||
|
||||
// ValidateBasic performs basic validation that doesn't involve state data.
|
||||
// Does not actually check the cryptographic signatures.
|
||||
func (commit *Commit) ValidateBasic() error {
|
||||
if commit.BlockID.IsZero() {
|
||||
return errors.New("Commit cannot be for nil block")
|
||||
|
@ -362,23 +367,23 @@ func (commit *Commit) ValidateBasic() error {
|
|||
}
|
||||
height, round := commit.Height(), commit.Round()
|
||||
|
||||
// validate the precommits
|
||||
// Validate the precommits.
|
||||
for _, precommit := range commit.Precommits {
|
||||
// It's OK for precommits to be missing.
|
||||
if precommit == nil {
|
||||
continue
|
||||
}
|
||||
// Ensure that all votes are precommits
|
||||
// Ensure that all votes are precommits.
|
||||
if precommit.Type != VoteTypePrecommit {
|
||||
return fmt.Errorf("Invalid commit vote. Expected precommit, got %v",
|
||||
precommit.Type)
|
||||
}
|
||||
// Ensure that all heights are the same
|
||||
// Ensure that all heights are the same.
|
||||
if precommit.Height != height {
|
||||
return fmt.Errorf("Invalid commit precommit height. Expected %v, got %v",
|
||||
height, precommit.Height)
|
||||
}
|
||||
// Ensure that all rounds are the same
|
||||
// Ensure that all rounds are the same.
|
||||
if precommit.Round != round {
|
||||
return fmt.Errorf("Invalid commit precommit round. Expected %v, got %v",
|
||||
round, precommit.Round)
|
||||
|
@ -413,19 +418,77 @@ func (commit *Commit) StringIndented(indent string) string {
|
|||
}
|
||||
return fmt.Sprintf(`Commit{
|
||||
%s BlockID: %v
|
||||
%s Precommits: %v
|
||||
%s Precommits:
|
||||
%s %v
|
||||
%s}#%v`,
|
||||
indent, commit.BlockID,
|
||||
indent, strings.Join(precommitStrings, "\n"+indent+" "),
|
||||
indent,
|
||||
indent, strings.Join(precommitStrings, "\n"+indent+" "),
|
||||
indent, commit.hash)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// SignedHeader is a header along with the commits that prove it
|
||||
// SignedHeader is a header along with the commits that prove it.
|
||||
type SignedHeader struct {
|
||||
Header *Header `json:"header"`
|
||||
Commit *Commit `json:"commit"`
|
||||
*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 {
|
||||
|
||||
// Make sure the header is consistent with the commit.
|
||||
if sh.Header == nil {
|
||||
return errors.New("SignedHeader missing header.")
|
||||
}
|
||||
if sh.Commit == nil {
|
||||
return errors.New("SignedHeader missing commit (precommit votes).")
|
||||
}
|
||||
// Check ChainID.
|
||||
if sh.ChainID != chainID {
|
||||
return fmt.Errorf("Header belongs to another chain '%s' not '%s'",
|
||||
sh.ChainID, chainID)
|
||||
}
|
||||
// Check Height.
|
||||
if sh.Commit.Height() != sh.Height {
|
||||
return fmt.Errorf("SignedHeader header and commit height mismatch: %v vs %v",
|
||||
sh.Height, sh.Commit.Height())
|
||||
}
|
||||
// Check Hash.
|
||||
hhash := sh.Hash()
|
||||
chash := sh.Commit.BlockID.Hash
|
||||
if !bytes.Equal(hhash, chash) {
|
||||
return fmt.Errorf("SignedHeader commit signs block %X, header is block %X",
|
||||
chash, hhash)
|
||||
}
|
||||
// ValidateBasic on the Commit.
|
||||
err := sh.Commit.ValidateBasic()
|
||||
if err != nil {
|
||||
return cmn.ErrorWrap(err, "commit.ValidateBasic failed during SignedHeader.ValidateBasic")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sh SignedHeader) String() string {
|
||||
return sh.StringIndented("")
|
||||
}
|
||||
|
||||
// StringIndented returns a string representation of the SignedHeader.
|
||||
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)
|
||||
return ""
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -110,5 +110,5 @@ func CanonicalTime(t time.Time) string {
|
|||
// Note that sending time over amino resets it to
|
||||
// local time, we need to force UTC here, so the
|
||||
// signatures match
|
||||
return t.UTC().Format(TimeFormat)
|
||||
return t.Round(0).UTC().Format(TimeFormat)
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRou
|
|||
return &Proposal{
|
||||
Height: height,
|
||||
Round: round,
|
||||
Timestamp: time.Now().UTC(),
|
||||
Timestamp: time.Now().Round(0).UTC(),
|
||||
BlockPartsHeader: blockPartsHeader,
|
||||
POLRound: polRound,
|
||||
POLBlockID: polBlockID,
|
||||
|
|
|
@ -67,11 +67,6 @@ func TestProto3Compatibility(t *testing.T) {
|
|||
Height: 150,
|
||||
Time: &proto3.Timestamp{Seconds: seconds, Nanos: nanos},
|
||||
NumTxs: 7,
|
||||
// This is not fully skipped in amino (yet) although it is empty:
|
||||
LastBlockID: &proto3.BlockID{
|
||||
PartsHeader: &proto3.PartSetHeader{
|
||||
},
|
||||
},
|
||||
TotalTxs: 100,
|
||||
LastCommitHash: []byte("commit hash"),
|
||||
DataHash: []byte("data hash"),
|
||||
|
|
|
@ -29,42 +29,57 @@ type ValidatorSet struct {
|
|||
totalVotingPower int64
|
||||
}
|
||||
|
||||
func NewValidatorSet(vals []*Validator) *ValidatorSet {
|
||||
validators := make([]*Validator, len(vals))
|
||||
for i, val := range vals {
|
||||
func NewValidatorSet(valz []*Validator) *ValidatorSet {
|
||||
if valz != nil && len(valz) == 0 {
|
||||
panic("validator set initialization slice cannot be an empty slice (but it can be nil)")
|
||||
}
|
||||
validators := make([]*Validator, len(valz))
|
||||
for i, val := range valz {
|
||||
validators[i] = val.Copy()
|
||||
}
|
||||
sort.Sort(ValidatorsByAddress(validators))
|
||||
vs := &ValidatorSet{
|
||||
vals := &ValidatorSet{
|
||||
Validators: validators,
|
||||
}
|
||||
|
||||
if len(vals) > 0 {
|
||||
vs.IncrementAccum(1)
|
||||
if len(valz) > 0 {
|
||||
vals.IncrementAccum(1)
|
||||
}
|
||||
|
||||
return vs
|
||||
return vals
|
||||
}
|
||||
|
||||
// Nil or empty validator sets are invalid.
|
||||
func (vals *ValidatorSet) IsNilOrEmpty() bool {
|
||||
return vals == nil || len(vals.Validators) == 0
|
||||
}
|
||||
|
||||
// Increment Accum and update the proposer on a copy, and return it.
|
||||
func (vals *ValidatorSet) CopyIncrementAccum(times int) *ValidatorSet {
|
||||
copy := vals.Copy()
|
||||
copy.IncrementAccum(times)
|
||||
return copy
|
||||
}
|
||||
|
||||
// IncrementAccum increments accum of each validator and updates the
|
||||
// proposer. Panics if validator set is empty.
|
||||
func (valSet *ValidatorSet) IncrementAccum(times int) {
|
||||
func (vals *ValidatorSet) IncrementAccum(times int) {
|
||||
|
||||
// Add VotingPower * times to each validator and order into heap.
|
||||
validatorsHeap := cmn.NewHeap()
|
||||
for _, val := range valSet.Validators {
|
||||
// check for overflow both multiplication and sum
|
||||
for _, val := range vals.Validators {
|
||||
// Check for overflow both multiplication and sum.
|
||||
val.Accum = safeAddClip(val.Accum, safeMulClip(val.VotingPower, int64(times)))
|
||||
validatorsHeap.PushComparable(val, accumComparable{val})
|
||||
}
|
||||
|
||||
// Decrement the validator with most accum times times
|
||||
// Decrement the validator with most accum times times.
|
||||
for i := 0; i < times; i++ {
|
||||
mostest := validatorsHeap.Peek().(*Validator)
|
||||
// mind underflow
|
||||
mostest.Accum = safeSubClip(mostest.Accum, valSet.TotalVotingPower())
|
||||
mostest.Accum = safeSubClip(mostest.Accum, vals.TotalVotingPower())
|
||||
|
||||
if i == times-1 {
|
||||
valSet.Proposer = mostest
|
||||
vals.Proposer = mostest
|
||||
} else {
|
||||
validatorsHeap.Update(mostest, accumComparable{mostest})
|
||||
}
|
||||
|
@ -72,36 +87,36 @@ func (valSet *ValidatorSet) IncrementAccum(times int) {
|
|||
}
|
||||
|
||||
// Copy each validator into a new ValidatorSet
|
||||
func (valSet *ValidatorSet) Copy() *ValidatorSet {
|
||||
validators := make([]*Validator, len(valSet.Validators))
|
||||
for i, val := range valSet.Validators {
|
||||
func (vals *ValidatorSet) Copy() *ValidatorSet {
|
||||
validators := make([]*Validator, len(vals.Validators))
|
||||
for i, val := range vals.Validators {
|
||||
// NOTE: must copy, since IncrementAccum updates in place.
|
||||
validators[i] = val.Copy()
|
||||
}
|
||||
return &ValidatorSet{
|
||||
Validators: validators,
|
||||
Proposer: valSet.Proposer,
|
||||
totalVotingPower: valSet.totalVotingPower,
|
||||
Proposer: vals.Proposer,
|
||||
totalVotingPower: vals.totalVotingPower,
|
||||
}
|
||||
}
|
||||
|
||||
// HasAddress returns true if address given is in the validator set, false -
|
||||
// otherwise.
|
||||
func (valSet *ValidatorSet) HasAddress(address []byte) bool {
|
||||
idx := sort.Search(len(valSet.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, valSet.Validators[i].Address) <= 0
|
||||
func (vals *ValidatorSet) HasAddress(address []byte) bool {
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, vals.Validators[i].Address) <= 0
|
||||
})
|
||||
return idx < len(valSet.Validators) && bytes.Equal(valSet.Validators[idx].Address, address)
|
||||
return idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address, address)
|
||||
}
|
||||
|
||||
// GetByAddress returns an index of the validator with address and validator
|
||||
// itself if found. Otherwise, -1 and nil are returned.
|
||||
func (valSet *ValidatorSet) GetByAddress(address []byte) (index int, val *Validator) {
|
||||
idx := sort.Search(len(valSet.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, valSet.Validators[i].Address) <= 0
|
||||
func (vals *ValidatorSet) GetByAddress(address []byte) (index int, val *Validator) {
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, vals.Validators[i].Address) <= 0
|
||||
})
|
||||
if idx < len(valSet.Validators) && bytes.Equal(valSet.Validators[idx].Address, address) {
|
||||
return idx, valSet.Validators[idx].Copy()
|
||||
if idx < len(vals.Validators) && bytes.Equal(vals.Validators[idx].Address, address) {
|
||||
return idx, vals.Validators[idx].Copy()
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
@ -109,45 +124,45 @@ func (valSet *ValidatorSet) GetByAddress(address []byte) (index int, val *Valida
|
|||
// GetByIndex returns the validator's address and validator itself by index.
|
||||
// It returns nil values if index is less than 0 or greater or equal to
|
||||
// len(ValidatorSet.Validators).
|
||||
func (valSet *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) {
|
||||
if index < 0 || index >= len(valSet.Validators) {
|
||||
func (vals *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) {
|
||||
if index < 0 || index >= len(vals.Validators) {
|
||||
return nil, nil
|
||||
}
|
||||
val = valSet.Validators[index]
|
||||
val = vals.Validators[index]
|
||||
return val.Address, val.Copy()
|
||||
}
|
||||
|
||||
// Size returns the length of the validator set.
|
||||
func (valSet *ValidatorSet) Size() int {
|
||||
return len(valSet.Validators)
|
||||
func (vals *ValidatorSet) Size() int {
|
||||
return len(vals.Validators)
|
||||
}
|
||||
|
||||
// TotalVotingPower returns the sum of the voting powers of all validators.
|
||||
func (valSet *ValidatorSet) TotalVotingPower() int64 {
|
||||
if valSet.totalVotingPower == 0 {
|
||||
for _, val := range valSet.Validators {
|
||||
func (vals *ValidatorSet) TotalVotingPower() int64 {
|
||||
if vals.totalVotingPower == 0 {
|
||||
for _, val := range vals.Validators {
|
||||
// mind overflow
|
||||
valSet.totalVotingPower = safeAddClip(valSet.totalVotingPower, val.VotingPower)
|
||||
vals.totalVotingPower = safeAddClip(vals.totalVotingPower, val.VotingPower)
|
||||
}
|
||||
}
|
||||
return valSet.totalVotingPower
|
||||
return vals.totalVotingPower
|
||||
}
|
||||
|
||||
// GetProposer returns the current proposer. If the validator set is empty, nil
|
||||
// is returned.
|
||||
func (valSet *ValidatorSet) GetProposer() (proposer *Validator) {
|
||||
if len(valSet.Validators) == 0 {
|
||||
func (vals *ValidatorSet) GetProposer() (proposer *Validator) {
|
||||
if len(vals.Validators) == 0 {
|
||||
return nil
|
||||
}
|
||||
if valSet.Proposer == nil {
|
||||
valSet.Proposer = valSet.findProposer()
|
||||
if vals.Proposer == nil {
|
||||
vals.Proposer = vals.findProposer()
|
||||
}
|
||||
return valSet.Proposer.Copy()
|
||||
return vals.Proposer.Copy()
|
||||
}
|
||||
|
||||
func (valSet *ValidatorSet) findProposer() *Validator {
|
||||
func (vals *ValidatorSet) findProposer() *Validator {
|
||||
var proposer *Validator
|
||||
for _, val := range valSet.Validators {
|
||||
for _, val := range vals.Validators {
|
||||
if proposer == nil || !bytes.Equal(val.Address, proposer.Address) {
|
||||
proposer = proposer.CompareAccum(val)
|
||||
}
|
||||
|
@ -157,12 +172,12 @@ func (valSet *ValidatorSet) findProposer() *Validator {
|
|||
|
||||
// Hash returns the Merkle root hash build using validators (as leaves) in the
|
||||
// set.
|
||||
func (valSet *ValidatorSet) Hash() []byte {
|
||||
if len(valSet.Validators) == 0 {
|
||||
func (vals *ValidatorSet) Hash() []byte {
|
||||
if len(vals.Validators) == 0 {
|
||||
return nil
|
||||
}
|
||||
hashers := make([]merkle.Hasher, len(valSet.Validators))
|
||||
for i, val := range valSet.Validators {
|
||||
hashers := make([]merkle.Hasher, len(vals.Validators))
|
||||
for i, val := range vals.Validators {
|
||||
hashers[i] = val
|
||||
}
|
||||
return merkle.SimpleHashFromHashers(hashers)
|
||||
|
@ -170,70 +185,70 @@ func (valSet *ValidatorSet) Hash() []byte {
|
|||
|
||||
// Add adds val to the validator set and returns true. It returns false if val
|
||||
// is already in the set.
|
||||
func (valSet *ValidatorSet) Add(val *Validator) (added bool) {
|
||||
func (vals *ValidatorSet) Add(val *Validator) (added bool) {
|
||||
val = val.Copy()
|
||||
idx := sort.Search(len(valSet.Validators), func(i int) bool {
|
||||
return bytes.Compare(val.Address, valSet.Validators[i].Address) <= 0
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(val.Address, vals.Validators[i].Address) <= 0
|
||||
})
|
||||
if idx >= len(valSet.Validators) {
|
||||
valSet.Validators = append(valSet.Validators, val)
|
||||
if idx >= len(vals.Validators) {
|
||||
vals.Validators = append(vals.Validators, val)
|
||||
// Invalidate cache
|
||||
valSet.Proposer = nil
|
||||
valSet.totalVotingPower = 0
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return true
|
||||
} else if bytes.Equal(valSet.Validators[idx].Address, val.Address) {
|
||||
} else if bytes.Equal(vals.Validators[idx].Address, val.Address) {
|
||||
return false
|
||||
} else {
|
||||
newValidators := make([]*Validator, len(valSet.Validators)+1)
|
||||
copy(newValidators[:idx], valSet.Validators[:idx])
|
||||
newValidators := make([]*Validator, len(vals.Validators)+1)
|
||||
copy(newValidators[:idx], vals.Validators[:idx])
|
||||
newValidators[idx] = val
|
||||
copy(newValidators[idx+1:], valSet.Validators[idx:])
|
||||
valSet.Validators = newValidators
|
||||
copy(newValidators[idx+1:], vals.Validators[idx:])
|
||||
vals.Validators = newValidators
|
||||
// Invalidate cache
|
||||
valSet.Proposer = nil
|
||||
valSet.totalVotingPower = 0
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates val and returns true. It returns false if val is not present
|
||||
// in the set.
|
||||
func (valSet *ValidatorSet) Update(val *Validator) (updated bool) {
|
||||
index, sameVal := valSet.GetByAddress(val.Address)
|
||||
func (vals *ValidatorSet) Update(val *Validator) (updated bool) {
|
||||
index, sameVal := vals.GetByAddress(val.Address)
|
||||
if sameVal == nil {
|
||||
return false
|
||||
}
|
||||
valSet.Validators[index] = val.Copy()
|
||||
vals.Validators[index] = val.Copy()
|
||||
// Invalidate cache
|
||||
valSet.Proposer = nil
|
||||
valSet.totalVotingPower = 0
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove deletes the validator with address. It returns the validator removed
|
||||
// and true. If returns nil and false if validator is not present in the set.
|
||||
func (valSet *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) {
|
||||
idx := sort.Search(len(valSet.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, valSet.Validators[i].Address) <= 0
|
||||
func (vals *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) {
|
||||
idx := sort.Search(len(vals.Validators), func(i int) bool {
|
||||
return bytes.Compare(address, vals.Validators[i].Address) <= 0
|
||||
})
|
||||
if idx >= len(valSet.Validators) || !bytes.Equal(valSet.Validators[idx].Address, address) {
|
||||
if idx >= len(vals.Validators) || !bytes.Equal(vals.Validators[idx].Address, address) {
|
||||
return nil, false
|
||||
}
|
||||
removedVal := valSet.Validators[idx]
|
||||
newValidators := valSet.Validators[:idx]
|
||||
if idx+1 < len(valSet.Validators) {
|
||||
newValidators = append(newValidators, valSet.Validators[idx+1:]...)
|
||||
removedVal := vals.Validators[idx]
|
||||
newValidators := vals.Validators[:idx]
|
||||
if idx+1 < len(vals.Validators) {
|
||||
newValidators = append(newValidators, vals.Validators[idx+1:]...)
|
||||
}
|
||||
valSet.Validators = newValidators
|
||||
vals.Validators = newValidators
|
||||
// Invalidate cache
|
||||
valSet.Proposer = nil
|
||||
valSet.totalVotingPower = 0
|
||||
vals.Proposer = nil
|
||||
vals.totalVotingPower = 0
|
||||
return removedVal, true
|
||||
}
|
||||
|
||||
// Iterate will run the given function over the set.
|
||||
func (valSet *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
||||
for i, val := range valSet.Validators {
|
||||
func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
||||
for i, val := range vals.Validators {
|
||||
stop := fn(i, val.Copy())
|
||||
if stop {
|
||||
break
|
||||
|
@ -241,87 +256,106 @@ func (valSet *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) {
|
|||
}
|
||||
}
|
||||
|
||||
// Verify that +2/3 of the set had signed the given signBytes
|
||||
func (valSet *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
|
||||
if valSet.Size() != len(commit.Precommits) {
|
||||
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", valSet.Size(), len(commit.Precommits))
|
||||
// Verify that +2/3 of the set had signed the given signBytes.
|
||||
func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error {
|
||||
if vals.Size() != len(commit.Precommits) {
|
||||
return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits))
|
||||
}
|
||||
if height != commit.Height() {
|
||||
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
|
||||
}
|
||||
if !blockID.Equals(commit.BlockID) {
|
||||
return fmt.Errorf("Invalid commit -- wrong block id: want %v got %v",
|
||||
blockID, commit.BlockID)
|
||||
}
|
||||
|
||||
talliedVotingPower := int64(0)
|
||||
round := commit.Round()
|
||||
|
||||
for idx, precommit := range commit.Precommits {
|
||||
// may be nil if validator skipped.
|
||||
if precommit == nil {
|
||||
continue
|
||||
continue // OK, some precommits can be missing.
|
||||
}
|
||||
if precommit.Height != height {
|
||||
return fmt.Errorf("Invalid commit -- wrong height: %v vs %v", height, precommit.Height)
|
||||
return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height)
|
||||
}
|
||||
if precommit.Round != round {
|
||||
return fmt.Errorf("Invalid commit -- wrong round: %v vs %v", round, precommit.Round)
|
||||
return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round)
|
||||
}
|
||||
if precommit.Type != VoteTypePrecommit {
|
||||
return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx)
|
||||
}
|
||||
_, val := valSet.GetByIndex(idx)
|
||||
// Validate signature
|
||||
_, val := vals.GetByIndex(idx)
|
||||
// Validate signature.
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
if !blockID.Equals(precommit.BlockID) {
|
||||
continue // Not an error, but doesn't count
|
||||
}
|
||||
// Good precommit!
|
||||
talliedVotingPower += val.VotingPower
|
||||
if blockID.Equals(precommit.BlockID) {
|
||||
talliedVotingPower += val.VotingPower
|
||||
} else {
|
||||
// It's OK that the BlockID doesn't match. We include stray
|
||||
// precommits to measure validator availability.
|
||||
}
|
||||
}
|
||||
|
||||
if talliedVotingPower > valSet.TotalVotingPower()*2/3 {
|
||||
if talliedVotingPower > vals.TotalVotingPower()*2/3 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Invalid commit -- insufficient voting power: got %v, needed %v",
|
||||
talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
|
||||
talliedVotingPower, (vals.TotalVotingPower()*2/3 + 1))
|
||||
}
|
||||
|
||||
// VerifyCommitAny will check to see if the set would
|
||||
// be valid with a different validator set.
|
||||
// VerifyFutureCommit will check to see if the set would be valid with a different
|
||||
// validator set.
|
||||
//
|
||||
// valSet is the validator set that we know
|
||||
// * over 2/3 of the power in old signed this block
|
||||
// vals is the old validator set that we know. Over 2/3 of the power in old
|
||||
// signed this block.
|
||||
//
|
||||
// newSet is the validator set that signed this block
|
||||
// * only votes from old are sufficient for 2/3 majority
|
||||
// in the new set as well
|
||||
// In Tendermint, 1/3 of the voting power can halt or fork the chain, but 1/3
|
||||
// can't make arbitrary state transitions. You still need > 2/3 Byzantine to
|
||||
// make arbitrary state transitions.
|
||||
//
|
||||
// That means that:
|
||||
// * 10% of the valset can't just declare themselves kings
|
||||
// * If the validator set is 3x old size, we need more proof to trust
|
||||
func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string,
|
||||
// To preserve this property in the light client, we also require > 2/3 of the
|
||||
// old vals to sign the future commit at H, that way we preserve the property
|
||||
// that if they weren't being truthful about the validator set at H (block hash
|
||||
// -> vals hash) or about the app state (block hash -> app hash) we can slash
|
||||
// > 2/3. Otherwise, the lite client isn't providing the same security
|
||||
// guarantees.
|
||||
//
|
||||
// Even if we added a slashing condition that if you sign a block header with
|
||||
// the wrong validator set, then we would only need > 1/3 of signatures from
|
||||
// the old vals on the new commit, it wouldn't be sufficient because the new
|
||||
// vals can be arbitrary and commit some arbitrary app hash.
|
||||
//
|
||||
// newSet is the validator set that signed this block. Only votes from new are
|
||||
// sufficient for 2/3 majority in the new set as well, for it to be a valid
|
||||
// commit.
|
||||
//
|
||||
// NOTE: This doesn't check whether the commit is a future commit, because the
|
||||
// current height isn't part of the ValidatorSet. Caller must check that the
|
||||
// commit height is greater than the height for this validator set.
|
||||
func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID string,
|
||||
blockID BlockID, height int64, commit *Commit) error {
|
||||
oldVals := vals
|
||||
|
||||
if newSet.Size() != len(commit.Precommits) {
|
||||
return cmn.NewError("Invalid commit -- wrong set size: %v vs %v", newSet.Size(), len(commit.Precommits))
|
||||
}
|
||||
if height != commit.Height() {
|
||||
return cmn.NewError("Invalid commit -- wrong height: %v vs %v", height, commit.Height())
|
||||
// Commit must be a valid commit for newSet.
|
||||
err := newSet.VerifyCommit(chainID, blockID, height, commit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check old voting power.
|
||||
oldVotingPower := int64(0)
|
||||
newVotingPower := int64(0)
|
||||
seen := map[int]bool{}
|
||||
round := commit.Round()
|
||||
|
||||
for idx, precommit := range commit.Precommits {
|
||||
// first check as in VerifyCommit
|
||||
if precommit == nil {
|
||||
continue
|
||||
}
|
||||
if precommit.Height != height {
|
||||
// return certerr.ErrHeightMismatch(height, precommit.Height)
|
||||
return cmn.NewError("Blocks don't match - %d vs %d", round, precommit.Round)
|
||||
}
|
||||
if precommit.Round != round {
|
||||
|
@ -330,54 +364,45 @@ func (valSet *ValidatorSet) VerifyCommitAny(newSet *ValidatorSet, chainID string
|
|||
if precommit.Type != VoteTypePrecommit {
|
||||
return cmn.NewError("Invalid commit -- not precommit @ index %v", idx)
|
||||
}
|
||||
if !blockID.Equals(precommit.BlockID) {
|
||||
continue // Not an error, but doesn't count
|
||||
}
|
||||
|
||||
// we only grab by address, ignoring unknown validators
|
||||
vi, ov := valSet.GetByAddress(precommit.ValidatorAddress)
|
||||
if ov == nil || seen[vi] {
|
||||
// See if this validator is in oldVals.
|
||||
idx, val := oldVals.GetByAddress(precommit.ValidatorAddress)
|
||||
if val == nil || seen[idx] {
|
||||
continue // missing or double vote...
|
||||
}
|
||||
seen[vi] = true
|
||||
seen[idx] = true
|
||||
|
||||
// Validate signature old school
|
||||
// Validate signature.
|
||||
precommitSignBytes := precommit.SignBytes(chainID)
|
||||
if !ov.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) {
|
||||
return cmn.NewError("Invalid commit -- invalid signature: %v", precommit)
|
||||
}
|
||||
// Good precommit!
|
||||
oldVotingPower += ov.VotingPower
|
||||
|
||||
// check new school
|
||||
_, cv := newSet.GetByIndex(idx)
|
||||
if cv.PubKey.Equals(ov.PubKey) {
|
||||
// make sure this is properly set in the current block as well
|
||||
newVotingPower += cv.VotingPower
|
||||
if blockID.Equals(precommit.BlockID) {
|
||||
oldVotingPower += val.VotingPower
|
||||
} else {
|
||||
// It's OK that the BlockID doesn't match. We include stray
|
||||
// precommits to measure validator availability.
|
||||
}
|
||||
}
|
||||
|
||||
if oldVotingPower <= valSet.TotalVotingPower()*2/3 {
|
||||
if oldVotingPower <= oldVals.TotalVotingPower()*2/3 {
|
||||
return cmn.NewError("Invalid commit -- insufficient old voting power: got %v, needed %v",
|
||||
oldVotingPower, (valSet.TotalVotingPower()*2/3 + 1))
|
||||
} else if newVotingPower <= newSet.TotalVotingPower()*2/3 {
|
||||
return cmn.NewError("Invalid commit -- insufficient cur voting power: got %v, needed %v",
|
||||
newVotingPower, (newSet.TotalVotingPower()*2/3 + 1))
|
||||
oldVotingPower, (oldVals.TotalVotingPower()*2/3 + 1))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (valSet *ValidatorSet) String() string {
|
||||
return valSet.StringIndented("")
|
||||
func (vals *ValidatorSet) String() string {
|
||||
return vals.StringIndented("")
|
||||
}
|
||||
|
||||
// String
|
||||
func (valSet *ValidatorSet) StringIndented(indent string) string {
|
||||
if valSet == nil {
|
||||
func (vals *ValidatorSet) StringIndented(indent string) string {
|
||||
if vals == nil {
|
||||
return "nil-ValidatorSet"
|
||||
}
|
||||
valStrings := []string{}
|
||||
valSet.Iterate(func(index int, val *Validator) bool {
|
||||
vals.Iterate(func(index int, val *Validator) bool {
|
||||
valStrings = append(valStrings, val.String())
|
||||
return false
|
||||
})
|
||||
|
@ -386,9 +411,9 @@ func (valSet *ValidatorSet) StringIndented(indent string) string {
|
|||
%s Validators:
|
||||
%s %v
|
||||
%s}`,
|
||||
indent, valSet.GetProposer().String(),
|
||||
indent, vals.GetProposer().String(),
|
||||
indent,
|
||||
indent, strings.Join(valStrings, "\n"+indent+" "),
|
||||
indent, strings.Join(valStrings, "\n"+indent+" "),
|
||||
indent)
|
||||
|
||||
}
|
||||
|
@ -399,18 +424,18 @@ func (valSet *ValidatorSet) StringIndented(indent string) string {
|
|||
// Sort validators by address
|
||||
type ValidatorsByAddress []*Validator
|
||||
|
||||
func (vs ValidatorsByAddress) Len() int {
|
||||
return len(vs)
|
||||
func (valz ValidatorsByAddress) Len() int {
|
||||
return len(valz)
|
||||
}
|
||||
|
||||
func (vs ValidatorsByAddress) Less(i, j int) bool {
|
||||
return bytes.Compare(vs[i].Address, vs[j].Address) == -1
|
||||
func (valz ValidatorsByAddress) Less(i, j int) bool {
|
||||
return bytes.Compare(valz[i].Address, valz[j].Address) == -1
|
||||
}
|
||||
|
||||
func (vs ValidatorsByAddress) Swap(i, j int) {
|
||||
it := vs[i]
|
||||
vs[i] = vs[j]
|
||||
vs[j] = it
|
||||
func (valz ValidatorsByAddress) Swap(i, j int) {
|
||||
it := valz[i]
|
||||
valz[i] = valz[j]
|
||||
valz[j] = it
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -434,16 +459,16 @@ func (ac accumComparable) Less(o interface{}) bool {
|
|||
// NOTE: PrivValidator are in order.
|
||||
// UNSTABLE
|
||||
func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []PrivValidator) {
|
||||
vals := make([]*Validator, numValidators)
|
||||
valz := make([]*Validator, numValidators)
|
||||
privValidators := make([]PrivValidator, numValidators)
|
||||
for i := 0; i < numValidators; i++ {
|
||||
val, privValidator := RandValidator(false, votingPower)
|
||||
vals[i] = val
|
||||
valz[i] = val
|
||||
privValidators[i] = privValidator
|
||||
}
|
||||
valSet := NewValidatorSet(vals)
|
||||
vals := NewValidatorSet(valz)
|
||||
sort.Sort(PrivValidatorsByAddress(privValidators))
|
||||
return valSet, privValidators
|
||||
return vals, privValidators
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -16,57 +16,58 @@ import (
|
|||
)
|
||||
|
||||
func TestValidatorSetBasic(t *testing.T) {
|
||||
for _, vset := range []*ValidatorSet{NewValidatorSet([]*Validator{}), NewValidatorSet(nil)} {
|
||||
assert.Panics(t, func() { vset.IncrementAccum(1) })
|
||||
assert.Panics(t, func() { NewValidatorSet([]*Validator{}) })
|
||||
|
||||
assert.EqualValues(t, vset, vset.Copy())
|
||||
assert.False(t, vset.HasAddress([]byte("some val")))
|
||||
idx, val := vset.GetByAddress([]byte("some val"))
|
||||
assert.Equal(t, -1, idx)
|
||||
assert.Nil(t, val)
|
||||
addr, val := vset.GetByIndex(-100)
|
||||
assert.Nil(t, addr)
|
||||
assert.Nil(t, val)
|
||||
addr, val = vset.GetByIndex(0)
|
||||
assert.Nil(t, addr)
|
||||
assert.Nil(t, val)
|
||||
addr, val = vset.GetByIndex(100)
|
||||
assert.Nil(t, addr)
|
||||
assert.Nil(t, val)
|
||||
assert.Zero(t, vset.Size())
|
||||
assert.Equal(t, int64(0), vset.TotalVotingPower())
|
||||
assert.Nil(t, vset.GetProposer())
|
||||
assert.Nil(t, vset.Hash())
|
||||
vset := NewValidatorSet(nil)
|
||||
assert.Panics(t, func() { vset.IncrementAccum(1) })
|
||||
|
||||
// add
|
||||
val = randValidator_()
|
||||
assert.True(t, vset.Add(val))
|
||||
assert.True(t, vset.HasAddress(val.Address))
|
||||
idx, val2 := vset.GetByAddress(val.Address)
|
||||
assert.Equal(t, 0, idx)
|
||||
assert.Equal(t, val, val2)
|
||||
addr, val2 = vset.GetByIndex(0)
|
||||
assert.Equal(t, []byte(val.Address), addr)
|
||||
assert.Equal(t, val, val2)
|
||||
assert.Equal(t, 1, vset.Size())
|
||||
assert.Equal(t, val.VotingPower, vset.TotalVotingPower())
|
||||
assert.Equal(t, val, vset.GetProposer())
|
||||
assert.NotNil(t, vset.Hash())
|
||||
assert.NotPanics(t, func() { vset.IncrementAccum(1) })
|
||||
assert.EqualValues(t, vset, vset.Copy())
|
||||
assert.False(t, vset.HasAddress([]byte("some val")))
|
||||
idx, val := vset.GetByAddress([]byte("some val"))
|
||||
assert.Equal(t, -1, idx)
|
||||
assert.Nil(t, val)
|
||||
addr, val := vset.GetByIndex(-100)
|
||||
assert.Nil(t, addr)
|
||||
assert.Nil(t, val)
|
||||
addr, val = vset.GetByIndex(0)
|
||||
assert.Nil(t, addr)
|
||||
assert.Nil(t, val)
|
||||
addr, val = vset.GetByIndex(100)
|
||||
assert.Nil(t, addr)
|
||||
assert.Nil(t, val)
|
||||
assert.Zero(t, vset.Size())
|
||||
assert.Equal(t, int64(0), vset.TotalVotingPower())
|
||||
assert.Nil(t, vset.GetProposer())
|
||||
assert.Nil(t, vset.Hash())
|
||||
|
||||
// update
|
||||
assert.False(t, vset.Update(randValidator_()))
|
||||
val.VotingPower = 100
|
||||
assert.True(t, vset.Update(val))
|
||||
// add
|
||||
val = randValidator_()
|
||||
assert.True(t, vset.Add(val))
|
||||
assert.True(t, vset.HasAddress(val.Address))
|
||||
idx, val2 := vset.GetByAddress(val.Address)
|
||||
assert.Equal(t, 0, idx)
|
||||
assert.Equal(t, val, val2)
|
||||
addr, val2 = vset.GetByIndex(0)
|
||||
assert.Equal(t, []byte(val.Address), addr)
|
||||
assert.Equal(t, val, val2)
|
||||
assert.Equal(t, 1, vset.Size())
|
||||
assert.Equal(t, val.VotingPower, vset.TotalVotingPower())
|
||||
assert.Equal(t, val, vset.GetProposer())
|
||||
assert.NotNil(t, vset.Hash())
|
||||
assert.NotPanics(t, func() { vset.IncrementAccum(1) })
|
||||
|
||||
// remove
|
||||
val2, removed := vset.Remove(randValidator_().Address)
|
||||
assert.Nil(t, val2)
|
||||
assert.False(t, removed)
|
||||
val2, removed = vset.Remove(val.Address)
|
||||
assert.Equal(t, val.Address, val2.Address)
|
||||
assert.True(t, removed)
|
||||
}
|
||||
// update
|
||||
assert.False(t, vset.Update(randValidator_()))
|
||||
val.VotingPower = 100
|
||||
assert.True(t, vset.Update(val))
|
||||
|
||||
// remove
|
||||
val2, removed := vset.Remove(randValidator_().Address)
|
||||
assert.Nil(t, val2)
|
||||
assert.False(t, removed)
|
||||
val2, removed = vset.Remove(val.Address)
|
||||
assert.Equal(t, val.Address, val2.Address)
|
||||
assert.True(t, removed)
|
||||
}
|
||||
|
||||
func TestCopy(t *testing.T) {
|
||||
|
|
|
@ -170,7 +170,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
|
|||
"Cannot find validator %d in valSet of size %d", valIndex, voteSet.valSet.Size())
|
||||
}
|
||||
|
||||
// Ensure that the signer has the right address
|
||||
// Ensure that the signer has the right address.
|
||||
if !bytes.Equal(valAddr, lookupAddr) {
|
||||
return false, errors.Wrapf(ErrVoteInvalidValidatorAddress,
|
||||
"vote.ValidatorAddress (%X) does not match address (%X) for vote.ValidatorIndex (%d)\nEnsure the genesis file is correct across all validators.",
|
||||
|
@ -190,7 +190,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
|
|||
return false, errors.Wrapf(err, "Failed to verify vote with ChainID %s and PubKey %s", voteSet.chainID, val.PubKey)
|
||||
}
|
||||
|
||||
// Add vote and get conflicting vote if any
|
||||
// Add vote and get conflicting vote if any.
|
||||
added, conflicting := voteSet.addVerifiedVote(vote, blockKey, val.VotingPower)
|
||||
if conflicting != nil {
|
||||
return added, NewConflictingVoteError(val, conflicting, vote)
|
||||
|
@ -201,7 +201,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
|
|||
return added, nil
|
||||
}
|
||||
|
||||
// Returns (vote, true) if vote exists for valIndex and blockKey
|
||||
// Returns (vote, true) if vote exists for valIndex and blockKey.
|
||||
func (voteSet *VoteSet) getVote(valIndex int, blockKey string) (vote *Vote, ok bool) {
|
||||
if existing := voteSet.votes[valIndex]; existing != nil && existing.BlockID.Key() == blockKey {
|
||||
return existing, true
|
||||
|
|
Loading…
Reference in New Issue