-
Notifications
You must be signed in to change notification settings - Fork 111
network: reject already-processed or stale approval/setup requests #1967
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
65086b3
e930bcf
8fb07e6
bb227ef
fc0b730
1bcdb58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,3 +14,4 @@ cmd/token_validation_service/out/ | |
| /site/ | ||
| coverage.out | ||
| /.codex/ | ||
| /plan.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /* | ||
| Copyright IBM Corp. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package replay | ||
|
|
||
| import "time" | ||
|
|
||
| // Backend identifies which Guard implementation to use. | ||
| type Backend string | ||
|
|
||
| const ( | ||
| // BackendMemory selects the in-memory Guard. See replay/memory. | ||
| BackendMemory Backend = "memory" | ||
| ) | ||
|
|
||
| // Config is the configuration for a replay Guard. | ||
| type Config struct { | ||
| // Backend selects the Guard implementation. Defaults to BackendMemory. | ||
| Backend Backend `yaml:"backend"` | ||
| // Window bounds how far a key's claimed Timestamp may lie from the guard's current time, | ||
| // in either direction, before it is rejected with ErrOutOfWindow. The window moves with | ||
| // the guard's clock. Window <= 0 disables the freshness check. | ||
| Window time.Duration `yaml:"window"` | ||
| // TTL is how long a seen key is remembered before it can be forgotten. Only meaningful | ||
| // for backends whose entries expire (e.g. BackendMemory). Must be at least 2*Window so an | ||
| // entry survives its entire potential-replay lifetime; backends enforce this floor. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This says backends enforce the floor, but |
||
| TTL time.Duration `yaml:"ttl"` | ||
| // MaxEntries caps the number of keys remembered at once (0 means unbounded). Only | ||
| // meaningful for backends with a bounded size (e.g. BackendMemory). | ||
| MaxEntries int `yaml:"maxEntries"` | ||
| } | ||
|
|
||
| // DefaultConfig returns the configuration used when none is explicitly set: an in-memory | ||
| // guard with a 5-minute freshness window, remembering a key for 10 minutes, bounded to | ||
| // 100000 entries. | ||
| func DefaultConfig() Config { | ||
| return Config{ | ||
| Backend: BackendMemory, | ||
| Window: 5 * time.Minute, | ||
| TTL: 10 * time.Minute, | ||
| MaxEntries: 100_000, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* | ||
| Copyright IBM Corp. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| // Package factory builds a replay.Guard from a replay.Config. It is a separate package from | ||
| // replay itself so that replay (which defines the Guard interface and Key type) does not need | ||
| // to import any concrete implementation, avoiding an import cycle. | ||
| package factory | ||
|
|
||
| import ( | ||
| "github.com/LFDT-Panurus/panurus/token/services/network/common/replay" | ||
| "github.com/LFDT-Panurus/panurus/token/services/network/common/replay/memory" | ||
| "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" | ||
| ) | ||
|
|
||
| // New builds the Guard selected by cfg. | ||
| func New(cfg replay.Config) (replay.Guard, error) { | ||
| switch cfg.Backend { | ||
| case replay.BackendMemory, "": | ||
| ttl := cfg.TTL | ||
| if floor := 2 * cfg.Window; ttl < floor { | ||
| ttl = floor | ||
| } | ||
|
|
||
| return memory.New(cfg.Window, ttl, cfg.MaxEntries), nil | ||
| default: | ||
| return nil, errors.Errorf("unknown replay guard backend: %s", cfg.Backend) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /* | ||
| Copyright IBM Corp. All Rights Reserved. | ||
|
|
||
| SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package factory_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/LFDT-Panurus/panurus/token/services/network/common/replay" | ||
| "github.com/LFDT-Panurus/panurus/token/services/network/common/replay/factory" | ||
| "github.com/LFDT-Panurus/panurus/token/services/network/common/replay/memory" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestNew_DefaultConfig(t *testing.T) { | ||
| g, err := factory.New(replay.DefaultConfig()) | ||
|
|
||
| require.NoError(t, err) | ||
| assert.IsType(t, &memory.Guard{}, g) | ||
| } | ||
|
|
||
| func TestNew_EmptyBackendDefaultsToMemory(t *testing.T) { | ||
| g, err := factory.New(replay.Config{}) | ||
|
|
||
| require.NoError(t, err) | ||
| assert.IsType(t, &memory.Guard{}, g) | ||
| } | ||
|
|
||
| func TestNew_UnknownBackend(t *testing.T) { | ||
| _, err := factory.New(replay.Config{Backend: "unknown"}) | ||
|
|
||
| require.Error(t, err) | ||
| assert.Contains(t, err.Error(), "unknown replay guard backend") | ||
| } | ||
|
|
||
| func TestNew_TTLFloorDerivedFromWindow(t *testing.T) { | ||
| // TTL is shorter than 2*Window: an entry must still be kept for the whole window | ||
| // lifecycle, so a key seen just inside the window must not be forgotten before it exits it. | ||
| g, err := factory.New(replay.Config{Window: time.Minute, TTL: time.Second, MaxEntries: 0}) | ||
| require.NoError(t, err) | ||
|
|
||
| now := time.Now() | ||
| key := replay.Key{TxID: "tx1", Creator: []byte("c"), Nonce: []byte("n"), Timestamp: now} | ||
| require.NoError(t, g.Check(context.Background(), key)) | ||
|
|
||
| time.Sleep(2 * time.Second) | ||
|
|
||
| // TTL alone (1s) would have evicted the entry by now; the floor (2*Window = 2m) must not. | ||
| err = g.Check(context.Background(), key) | ||
| require.ErrorIs(t, err, replay.ErrAlreadyProcessed) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.