From b150c865f95a41a7672de95be9d7541dfc6fe8c2 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Thu, 13 Jul 2017 22:11:15 +0200 Subject: [PATCH] Add a light-client provider in the kvstore --- modules/ibc/provider.go | 46 +++++++++--- modules/ibc/provider_test.go | 136 +++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 modules/ibc/provider_test.go diff --git a/modules/ibc/provider.go b/modules/ibc/provider.go index a28e6af95..91aebb77a 100644 --- a/modules/ibc/provider.go +++ b/modules/ibc/provider.go @@ -1,17 +1,24 @@ package ibc import ( + wire "github.com/tendermint/go-wire" "github.com/tendermint/light-client/certifiers" "github.com/tendermint/basecoin/stack" "github.com/tendermint/basecoin/state" ) +const ( + prefixHash = "v" + prefixHeight = "h" + prefixPacket = "p" +) + // newCertifier loads up the current state of this chain to make a proper func newCertifier(chainID string, store state.KVStore) (*certifiers.InquiringCertifier, error) { // each chain has their own prefixed subspace space := stack.PrefixedStore(chainID, store) - p := dbProvider{space} + p := newDBProvider(space) // this gets the most recent verified seed seed, err := certifiers.LatestSeed(p) @@ -27,18 +34,41 @@ func newCertifier(chainID string, store state.KVStore) (*certifiers.InquiringCer // dbProvider wraps our kv store so it integrates with light-client verification type dbProvider struct { - store state.KVStore + byHash state.KVStore + byHeight *state.Span } -var _ certifiers.Provider = dbProvider{} +func newDBProvider(store state.KVStore) *dbProvider { + return &dbProvider{ + byHash: stack.PrefixedStore(prefixHash, store), + byHeight: state.NewSpan(stack.PrefixedStore(prefixHeight, store)), + } +} -func (d dbProvider) StoreSeed(seed certifiers.Seed) error { +var _ certifiers.Provider = &dbProvider{} + +func (d *dbProvider) StoreSeed(seed certifiers.Seed) error { + // TODO: don't duplicate data.... + b := wire.BinaryBytes(seed) + d.byHash.Set(seed.Hash(), b) + d.byHeight.Set(uint64(seed.Height()), b) return nil } -func (d dbProvider) GetByHeight(h int) (certifiers.Seed, error) { - return certifiers.Seed{}, certifiers.ErrSeedNotFound() +func (d *dbProvider) GetByHeight(h int) (seed certifiers.Seed, err error) { + b, _ := d.byHeight.LTE(uint64(h)) + if b == nil { + return seed, certifiers.ErrSeedNotFound() + } + err = wire.ReadBinaryBytes(b, &seed) + return } -func (d dbProvider) GetByHash(hash []byte) (certifiers.Seed, error) { - return certifiers.Seed{}, certifiers.ErrSeedNotFound() + +func (d *dbProvider) GetByHash(hash []byte) (seed certifiers.Seed, err error) { + b := d.byHash.Get(hash) + if b == nil { + return seed, certifiers.ErrSeedNotFound() + } + err = wire.ReadBinaryBytes(b, &seed) + return } diff --git a/modules/ibc/provider_test.go b/modules/ibc/provider_test.go new file mode 100644 index 000000000..6028570ea --- /dev/null +++ b/modules/ibc/provider_test.go @@ -0,0 +1,136 @@ +package ibc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/basecoin/state" + "github.com/tendermint/light-client/certifiers" +) + +func assertSeedEqual(t *testing.T, s, s2 certifiers.Seed) { + assert := assert.New(t) + assert.Equal(s.Height(), s2.Height()) + assert.Equal(s.Hash(), s2.Hash()) + // TODO: more +} + +func TestProviderStore(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // make a few seeds + keys := certifiers.GenValKeys(2) + seeds := makeSeeds(keys, 4, "some-chain", "demo-store") + + // make a provider + store := state.NewMemKVStore() + p := newDBProvider(store) + + // check it... + _, err := p.GetByHeight(20) + require.NotNil(err) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + // add a seed + for _, s := range seeds { + err = p.StoreSeed(s) + require.Nil(err) + } + + // make sure we get it... + s := seeds[0] + val, err := p.GetByHeight(s.Height()) + if assert.Nil(err) { + assertSeedEqual(t, s, val) + } + + // make sure we get higher + val, err = p.GetByHeight(s.Height() + 2) + if assert.Nil(err) { + assertSeedEqual(t, s, val) + } + + // below is nothing + _, err = p.GetByHeight(s.Height() - 2) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + // make sure we get highest + val, err = certifiers.LatestSeed(p) + if assert.Nil(err) { + assertSeedEqual(t, seeds[3], val) + } + + // make sure by hash also (note all have same hash, so overwritten) + val, err = p.GetByHash(seeds[1].Hash()) + if assert.Nil(err) { + assertSeedEqual(t, seeds[3], val) + } +} + +func TestDBProvider(t *testing.T) { + store := state.NewMemKVStore() + p := newDBProvider(store) + checkProvider(t, p, "test-db", "bling") +} + +func makeSeeds(keys certifiers.ValKeys, count int, chainID, app string) []certifiers.Seed { + appHash := []byte(app) + seeds := make([]certifiers.Seed, 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 := 20 + 10*i + check := keys.GenCheckpoint(chainID, h, nil, vals, appHash, 0, len(keys)) + seeds[i] = certifiers.Seed{check, vals} + } + return seeds +} + +func checkProvider(t *testing.T, p certifiers.Provider, chainID, app string) { + assert, require := assert.New(t), require.New(t) + keys := certifiers.GenValKeys(5) + count := 10 + + // make a bunch of seeds... + seeds := makeSeeds(keys, count, chainID, app) + + // check provider is empty + seed, err := p.GetByHeight(20) + require.NotNil(err) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + seed, err = p.GetByHash(seeds[3].Hash()) + require.NotNil(err) + assert.True(certifiers.IsSeedNotFoundErr(err)) + + // now add them all to the provider + for _, s := range seeds { + err = p.StoreSeed(s) + require.Nil(err) + // and make sure we can get it back + s2, err := p.GetByHash(s.Hash()) + assert.Nil(err) + assertSeedEqual(t, s, s2) + // by height as well + s2, err = p.GetByHeight(s.Height()) + assert.Nil(err) + assertSeedEqual(t, s, s2) + } + + // make sure we get the last hash if we overstep + seed, err = p.GetByHeight(5000) + if assert.Nil(err) { + assertSeedEqual(t, seeds[count-1], seed) + } + + // and middle ones as well + seed, err = p.GetByHeight(47) + if assert.Nil(err) { + // we only step by 10, so 40 must be the one below this + assert.Equal(40, seed.Height()) + } + +}