Skip to content

Commit abf8068

Browse files
RozerxshashankAKRAM@il.ibm.com
authored andcommitted
perf(benchmark): add benchmarks and documentation for fabtoken
Signed-off-by: Shashank <yshashank959@gmail.com> fix(multisig,boolpolicy): verify spend tx matches approved SpendRequest (#1691) Signed-off-by: SuyashAlphaC <suyashagrawal862@gmail.com> fsc v0.11.0 (#1702) Signed-off-by: Angelo De Caro <adc@zurich.ibm.com> feat(ttx): add versioned envelope for interactive protocol messages (#1700) Signed-off-by: SuyashAlphaC <suyashagrawal862@gmail.com> fix(recovery): unblock queue head by marking NotFound orphans Deleted after grace period (#1708) Signed-off-by: Evan <evanyan@sign.global> fix: cachedFetcher.update() no longer blocks token reads during DB refresh (#1535) Signed-off-by: Nitesh <nitesh@example.com> Signed-off-by: Nitesh Kumar <niteshkumar121411@gmail.com> Signed-off-by: NETIZEN-11 <kumarnitesh979875@gmail.com> perf(bulletproof): optimize IPA prover with batched MSMs (#1719) Signed-off-by: Ankit Basu <ankitbasu14@gmail.com> replace mutex with context aware semaphore (#1616) Signed-off-by: Hayim.Shaul@ibm.com <hayimsha@fhe3.haifa.ibm.com> refactor(recovery): return RecoveryClaim from ClaimPendingTransactions (#1715) Signed-off-by: Evan <evanyan@sign.global> Adding ZKP Benchmarking to test overhead of FSC nodes on TPS Signed-off-by: Effi-S <effi.szt@gmail.com> Signed-off-by: AKRAM@il.ibm.com <akram@akramb.vpc.cloud9.ibm.com> Fix #1635: Prevent audit lock starvation via defer pattern - Enhanced Audit() documentation requiring immediate defer Release() - Fixed integration test callers with proper error handling - Added unit tests for lock acquisition error handling - Leverages existing semaphore.Weighted for context-aware acquisition (PR #1616) - Ensures locks always released via defer, preventing DoS attacks Signed-off-by: AKRAM@il.ibm.com <akram@akramb.vpc.cloud9.ibm.com>
1 parent b1d9869 commit abf8068

5 files changed

Lines changed: 97 additions & 5 deletions

File tree

integration/token/fungible/views/auditor.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ func (a *AuditView) Call(context view.Context) (interface{}, error) {
5757
// extract inputs and outputs
5858
logger.Debugf("AuditView: audit [%s]", tx.ID())
5959
inputs, outputs, err := auditor.Audit(context.Context(), tx)
60-
assert.NoError(err, "failed retrieving inputs and outputs")
61-
logger.Debugf("AuditView: audit done [%s]", tx.ID())
60+
if err != nil {
61+
return nil, errors.Wrapf(err, "failed retrieving inputs and outputs")
62+
}
6263
defer auditor.Release(context.Context(), tx)
64+
logger.Debugf("AuditView: audit done [%s]", tx.ID())
6365

6466
logger.Debugf("AuditView: [%s] get query executor... ", tx.ID())
6567

integration/token/interop/views/auditor.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"math/big"
1212
"time"
1313

14+
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
1415
"github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/assert"
1516
"github.com/hyperledger-labs/fabric-smart-client/platform/view/view"
1617
"github.com/hyperledger-labs/fabric-token-sdk/token"
@@ -40,7 +41,9 @@ func (a *AuditView) Call(context view.Context) (interface{}, error) {
4041

4142
// extract inputs and outputs
4243
inputs, outputs, err := auditor.Audit(context.Context(), tx)
43-
assert.NoError(err, "failed retrieving inputs and outputs")
44+
if err != nil {
45+
return nil, errors.Wrapf(err, "failed retrieving inputs and outputs")
46+
}
4447
defer auditor.Release(context.Context(), tx)
4548

4649
// For example, all payments of an amount less than or equal to payment limit is valid

token/services/auditor/auditor.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,22 @@ func (a *Service) Validate(ctx context.Context, request *token.Request) error {
111111
}
112112

113113
// Audit extracts the list of inputs and outputs from the passed transaction.
114-
// In addition, the Audit locks the enrollment named ids.
115-
// Release must be invoked in case
114+
// In addition, Audit acquires locks on the enrollment IDs involved in the transaction.
115+
// The caller MUST call Release() to unlock these enrollment IDs after processing.
116+
//
117+
// IMPORTANT: The defer Release() statement MUST be placed immediately after checking
118+
// the error returned by Audit(). This ensures locks are released even if subsequent
119+
// operations fail. Example:
120+
//
121+
// inputs, outputs, err := auditor.Audit(ctx, tx)
122+
// if err != nil {
123+
// return errors.Wrap(err, "audit failed")
124+
// }
125+
// defer auditor.Release(ctx, tx)
126+
//
127+
// Note: The semaphore-based locking mechanism handles context cancellation during
128+
// lock acquisition (see PR #1616), ensuring proper cleanup in case of timeouts or
129+
// cancellations.
116130
func (a *Service) Audit(ctx context.Context, tx Transaction) (*token.InputStream, *token.OutputStream, error) {
117131
start := time.Now()
118132
logger.DebugfContext(ctx, "audit transaction [%s]....", tx.ID())

token/services/auditor/auditor_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,63 @@ func TestService_Audit_Success(t *testing.T) {
309309
assert.NotNil(t, outputs)
310310
}
311311

312+
// TestService_Audit_LockAcquisitionFailure verifies that when AcquireLocks() fails
313+
// due to context cancellation, the error is properly returned and no locks are held.
314+
func TestService_Audit_LockAcquisitionFailure(t *testing.T) {
315+
// Create a StoreService with real lock mechanism
316+
storeService := newTestStoreService(t, newFakeStore())
317+
318+
// First, acquire locks directly on enrollment IDs to simulate lock contention
319+
ctx := context.Background()
320+
err := storeService.AcquireLocks(ctx, "blocking-anchor", "test-eid-1", "test-eid-2")
321+
require.NoError(t, err)
322+
defer storeService.ReleaseLocks(ctx, "blocking-anchor")
323+
324+
// Now try to acquire the same locks with a cancelled context
325+
cancelledCtx, cancel := context.WithCancel(context.Background())
326+
cancel() // Cancel immediately to simulate timeout/cancellation
327+
328+
err = storeService.AcquireLocks(cancelledCtx, "tx-lock-fail", "test-eid-1", "test-eid-2")
329+
require.Error(t, err)
330+
assert.Contains(t, err.Error(), "context canceled")
331+
332+
// Verify no locks are held for the failed transaction by checking the anchor is not stored
333+
storeService.ReleaseLocks(ctx, "tx-lock-fail") // Should be a no-op since locks weren't acquired
334+
}
335+
336+
// TestService_Audit_ContextCancellationDuringAcquisition verifies that when context
337+
// is cancelled or times out during lock acquisition, the semaphore automatically rolls
338+
// back acquired locks and returns an error, allowing subsequent acquisitions to succeed.
339+
func TestService_Audit_ContextCancellationDuringAcquisition(t *testing.T) {
340+
// Create a StoreService with real lock mechanism
341+
storeService := newTestStoreService(t, newFakeStore())
342+
343+
// Acquire locks to create contention
344+
ctx := context.Background()
345+
err := storeService.AcquireLocks(ctx, "blocking-anchor", "test-eid-1", "test-eid-2")
346+
require.NoError(t, err)
347+
348+
// Create a context with a very short timeout
349+
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1)
350+
defer cancel()
351+
352+
// This should fail due to context timeout while waiting for locks
353+
err = storeService.AcquireLocks(timeoutCtx, "tx-ctx-cancel", "test-eid-1", "test-eid-2")
354+
require.Error(t, err)
355+
assert.True(t, errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled),
356+
"expected context cancellation error, got: %v", err)
357+
358+
// Release the blocking locks
359+
storeService.ReleaseLocks(ctx, "blocking-anchor")
360+
361+
// Verify the semaphore automatically rolled back by attempting acquisition with fresh context
362+
err = storeService.AcquireLocks(context.Background(), "tx-after-cancel", "test-eid-1", "test-eid-2")
363+
require.NoError(t, err)
364+
365+
// Clean up
366+
storeService.ReleaseLocks(ctx, "tx-after-cancel")
367+
}
368+
312369
func TestService_Audit_DBCleanSuccess(t *testing.T) {
313370
fakeStore := newFakeStore()
314371
fakeStore.GetStatusReturns(0, "", errors.New("db status err"))

token/services/ttx/auditor.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ func (a *Auditor) Validate(tx *Transaction) error {
5959
return a.Service.Validate(tx.Context, tx.TokenRequest)
6060
}
6161

62+
// Audit extracts the list of inputs and outputs from the passed transaction.
63+
// In addition, Audit acquires locks on the enrollment IDs involved in the transaction.
64+
// The caller MUST call Release() to unlock these enrollment IDs after processing.
65+
//
66+
// IMPORTANT: The defer Release() statement MUST be placed immediately after checking
67+
// the error returned by Audit(). This ensures locks are released even if subsequent
68+
// operations fail. Example:
69+
//
70+
// inputs, outputs, err := auditor.Audit(ctx, tx)
71+
// if err != nil {
72+
// return errors.Wrap(err, "audit failed")
73+
// }
74+
// defer auditor.Release(ctx, tx)
75+
//
76+
// Note: The semaphore-based locking mechanism handles context cancellation during
77+
// lock acquisition, ensuring proper cleanup in case of timeouts or cancellations.
6278
func (a *Auditor) Audit(ctx context.Context, tx *Transaction) (*token.InputStream, *token.OutputStream, error) {
6379
return a.Service.Audit(ctx, tx)
6480
}

0 commit comments

Comments
 (0)