statesync: implement p2p state provider (#6807)

This commit is contained in:
Callum Waters
2021-09-02 13:19:18 +02:00
committed by GitHub
parent 511bd3eb7f
commit bda948e814
37 changed files with 1890 additions and 869 deletions
+34 -22
View File
@@ -52,6 +52,8 @@ const (
// 10s is sufficient for most networks.
defaultMaxBlockLag = 10 * time.Second
defaultProviderTimeout = 10 * time.Second
)
// Option sets a parameter for the light client.
@@ -61,9 +63,7 @@ type Option func(*Client)
// check the blocks (every block, in ascending height order). Note this is
// much slower than SkippingVerification, albeit more secure.
func SequentialVerification() Option {
return func(c *Client) {
c.verificationMode = sequential
}
return func(c *Client) { c.verificationMode = sequential }
}
// SkippingVerification option configures the light client to skip blocks as
@@ -87,24 +87,18 @@ func SkippingVerification(trustLevel tmmath.Fraction) Option {
// the h amount of light blocks will be removed from the store.
// Default: 1000. A pruning size of 0 will not prune the light client at all.
func PruningSize(h uint16) Option {
return func(c *Client) {
c.pruningSize = h
}
return func(c *Client) { c.pruningSize = h }
}
// Logger option can be used to set a logger for the client.
func Logger(l log.Logger) Option {
return func(c *Client) {
c.logger = l
}
return func(c *Client) { c.logger = l }
}
// MaxClockDrift defines how much new header's time can drift into
// the future relative to the light clients local time. Default: 10s.
func MaxClockDrift(d time.Duration) Option {
return func(c *Client) {
c.maxClockDrift = d
}
return func(c *Client) { c.maxClockDrift = d }
}
// MaxBlockLag represents the maximum time difference between the realtime
@@ -116,9 +110,13 @@ func MaxClockDrift(d time.Duration) Option {
// was 12:00. Then the lag here is 5 minutes.
// Default: 10s
func MaxBlockLag(d time.Duration) Option {
return func(c *Client) {
c.maxBlockLag = d
}
return func(c *Client) { c.maxBlockLag = d }
}
// Provider timeout is the maximum time that the light client will wait for a
// provider to respond with a light block.
func ProviderTimeout(d time.Duration) Option {
return func(c *Client) { c.providerTimeout = d }
}
// Client represents a light client, connected to a single chain, which gets
@@ -133,6 +131,7 @@ type Client struct {
trustLevel tmmath.Fraction
maxClockDrift time.Duration
maxBlockLag time.Duration
providerTimeout time.Duration
// Mutex for locking during changes of the light clients providers
providerMutex tmsync.Mutex
@@ -197,12 +196,13 @@ func NewClient(
chainID: chainID,
trustingPeriod: trustOptions.Period,
verificationMode: skipping,
trustLevel: DefaultTrustLevel,
maxClockDrift: defaultMaxClockDrift,
maxBlockLag: defaultMaxBlockLag,
primary: primary,
witnesses: witnesses,
trustedStore: trustedStore,
trustLevel: DefaultTrustLevel,
maxClockDrift: defaultMaxClockDrift,
maxBlockLag: defaultMaxBlockLag,
providerTimeout: defaultProviderTimeout,
pruningSize: defaultPruningSize,
logger: log.NewNopLogger(),
}
@@ -693,7 +693,9 @@ func (c *Client) verifySkipping(
if depth == len(blockCache)-1 {
// schedule what the next height we need to fetch is
pivotHeight := c.schedule(verifiedBlock.Height, blockCache[depth].Height)
interimBlock, providerErr := source.LightBlock(ctx, pivotHeight)
subCtx, cancel := context.WithTimeout(ctx, c.providerTimeout)
defer cancel()
interimBlock, providerErr := c.getLightBlock(subCtx, source, pivotHeight)
if providerErr != nil {
return nil, ErrVerificationFailed{From: verifiedBlock.Height, To: pivotHeight, Reason: providerErr}
}
@@ -930,7 +932,7 @@ func (c *Client) backwards(
// any other error, the primary is permanently dropped and is replaced by a witness.
func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*types.LightBlock, error) {
c.providerMutex.Lock()
l, err := c.primary.LightBlock(ctx, height)
l, err := c.getLightBlock(ctx, c.primary, height)
c.providerMutex.Unlock()
switch err {
@@ -957,6 +959,16 @@ func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*type
}
}
func (c *Client) getLightBlock(ctx context.Context, p provider.Provider, height int64) (*types.LightBlock, error) {
subCtx, cancel := context.WithTimeout(ctx, c.providerTimeout)
defer cancel()
l, err := p.LightBlock(subCtx, height)
if err == context.DeadlineExceeded || ctx.Err() != nil {
return nil, provider.ErrNoResponse
}
return l, err
}
// NOTE: requires a providerMutex lock
func (c *Client) removeWitnesses(indexes []int) error {
// check that we will still have witnesses remaining
@@ -989,7 +1001,7 @@ func (c *Client) findNewPrimary(ctx context.Context, height int64, remove bool)
c.providerMutex.Lock()
defer c.providerMutex.Unlock()
if len(c.witnesses) <= 1 {
if len(c.witnesses) < 1 {
return nil, ErrNoWitnesses
}
@@ -1001,7 +1013,7 @@ func (c *Client) findNewPrimary(ctx context.Context, height int64, remove bool)
)
// send out a light block request to all witnesses
subctx, cancel := context.WithCancel(ctx)
subctx, cancel := context.WithTimeout(ctx, c.providerTimeout)
defer cancel()
for index := range c.witnesses {
wg.Add(1)
+24 -43
View File
@@ -724,51 +724,32 @@ func TestClient_BackwardsVerification(t *testing.T) {
}
{
testCases := []struct {
headers map[int64]*types.SignedHeader
vals map[int64]*types.ValidatorSet
}{
{
// 7) provides incorrect height
headers: map[int64]*types.SignedHeader{
2: keys.GenSignedHeader(chainID, 1, bTime.Add(30*time.Minute), nil, vals, vals,
hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys)),
3: h3,
},
vals: valSet,
},
{
// 8) provides incorrect hash
headers: map[int64]*types.SignedHeader{
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
hash("app_hash2"), hash("cons_hash23"), hash("results_hash30"), 0, len(keys)),
3: h3,
},
vals: valSet,
},
// 8) provides incorrect hash
headers := map[int64]*types.SignedHeader{
2: keys.GenSignedHeader(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
hash("app_hash2"), hash("cons_hash23"), hash("results_hash30"), 0, len(keys)),
3: h3,
}
vals := valSet
mockNode := mockNodeFromHeadersAndVals(headers, vals)
c, err := light.NewClient(
ctx,
chainID,
light.TrustOptions{
Period: 1 * time.Hour,
Height: 3,
Hash: h3.Hash(),
},
mockNode,
[]provider.Provider{mockNode},
dbs.New(dbm.NewMemDB()),
light.Logger(log.TestingLogger()),
)
require.NoError(t, err)
for idx, tc := range testCases {
mockNode := mockNodeFromHeadersAndVals(tc.headers, tc.vals)
c, err := light.NewClient(
ctx,
chainID,
light.TrustOptions{
Period: 1 * time.Hour,
Height: 3,
Hash: h3.Hash(),
},
mockNode,
[]provider.Provider{mockNode},
dbs.New(dbm.NewMemDB()),
light.Logger(log.TestingLogger()),
)
require.NoError(t, err, idx)
_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour).Add(1*time.Second))
assert.Error(t, err, idx)
mockNode.AssertExpectations(t)
}
_, err = c.VerifyLightBlockAtHeight(ctx, 2, bTime.Add(1*time.Hour).Add(1*time.Second))
assert.Error(t, err)
mockNode.AssertExpectations(t)
}
}
+4 -4
View File
@@ -110,7 +110,7 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig
func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan error, h *types.SignedHeader,
witness provider.Provider, witnessIndex int) {
lightBlock, err := witness.LightBlock(ctx, h.Height)
lightBlock, err := c.getLightBlock(ctx, witness, h.Height)
switch err {
// no error means we move on to checking the hash of the two headers
case nil:
@@ -331,7 +331,7 @@ func (c *Client) examineConflictingHeaderAgainstTrace(
if traceBlock.Height == targetBlock.Height {
sourceBlock = targetBlock
} else {
sourceBlock, err = source.LightBlock(ctx, traceBlock.Height)
sourceBlock, err = c.getLightBlock(ctx, source, traceBlock.Height)
if err != nil {
return nil, nil, fmt.Errorf("failed to examine trace: %w", err)
}
@@ -379,7 +379,7 @@ func (c *Client) getTargetBlockOrLatest(
height int64,
witness provider.Provider,
) (bool, *types.LightBlock, error) {
lightBlock, err := witness.LightBlock(ctx, 0)
lightBlock, err := c.getLightBlock(ctx, witness, 0)
if err != nil {
return false, nil, err
}
@@ -394,7 +394,7 @@ func (c *Client) getTargetBlockOrLatest(
// the witness has caught up. We recursively call the function again. However in order
// to avoud a wild goose chase where the witness sends us one header below and one header
// above the height we set a timeout to the context
lightBlock, err := witness.LightBlock(ctx, height)
lightBlock, err := c.getLightBlock(ctx, witness, height)
return true, lightBlock, err
}