Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions token/core/fabtoken/v1/audit/auditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@ type ValidateTransferAuditFunc = common.ValidateTransferAuditFunc[*setup.PublicP
type AuditContext = common.AuditContext[*setup.PublicParams, *actions.IssueAction, *actions.TransferAction, driver.Deserializer]

// ActionDeserializer deserializes fabtoken actions.
type ActionDeserializer struct{}
type ActionDeserializer struct {
// Limits are the configured resource limits stamped onto each action before
// deserialization, so the audit path enforces the same policy as the validator.
Limits driver.ResourceLimits
}

// DeserializeActions deserializes issue and transfer actions from a token request.
func (a *ActionDeserializer) DeserializeActions(tr *driver.TokenRequest) ([]*actions.IssueAction, []*actions.TransferAction, error) {
issues := tr.GetIssues()
issueActions := make([]*actions.IssueAction, len(issues))
for i := range issues {
ia := &actions.IssueAction{}
ia.SetLimits(a.Limits)
if err := ia.Deserialize(issues[i]); err != nil {
return nil, nil, err
}
Expand All @@ -46,6 +51,7 @@ func (a *ActionDeserializer) DeserializeActions(tr *driver.TokenRequest) ([]*act
transferActions := make([]*actions.TransferAction, len(transfers))
for i := range transfers {
ta := &actions.TransferAction{}
ta.SetLimits(a.Limits)
if err := ta.Deserialize(transfers[i]); err != nil {
return nil, nil, err
}
Expand All @@ -59,7 +65,11 @@ func (a *ActionDeserializer) DeserializeActions(tr *driver.TokenRequest) ([]*act
type Auditor = common.Auditor[*setup.PublicParams, *actions.IssueAction, *actions.TransferAction, driver.Deserializer]

// NewAuditor creates a new Auditor for fabtoken validation.
func NewAuditor(logger logging.Logger, tracer trace.Tracer, deserializer driver.Deserializer, pp *setup.PublicParams, precision uint64) *Auditor {
//
// limits are the configured resource limits; they are threaded into the
// ActionDeserializer and applied to each action before deserialization so the
// audit path enforces the same policy as the validator (see validator.go).
func NewAuditor(logger logging.Logger, tracer trace.Tracer, deserializer driver.Deserializer, pp *setup.PublicParams, precision uint64, limits driver.ResourceLimits) *Auditor {
issueValidators := []ValidateIssueAuditFunc{
IssueAuditValidate(precision),
}
Expand All @@ -73,7 +83,7 @@ func NewAuditor(logger logging.Logger, tracer trace.Tracer, deserializer driver.
tracer,
pp,
deserializer,
&ActionDeserializer{},
&ActionDeserializer{Limits: limits},
issueValidators,
transferValidators,
)
Expand Down
100 changes: 100 additions & 0 deletions token/core/fabtoken/v1/audit/auditor_limits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package audit

import (
"testing"

protoactions "github.com/LFDT-Panurus/panurus/token/core/fabtoken/protos-go/v1/actions"
"github.com/LFDT-Panurus/panurus/token/core/fabtoken/v1/actions"
"github.com/LFDT-Panurus/panurus/token/driver"
"github.com/LFDT-Panurus/panurus/token/driver/protos-go/v1/request"
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto"
"github.com/stretchr/testify/require"
)

// marshalIssueAction builds a serialized fabtoken issue action with the given
// number of outputs.
func marshalIssueAction(t *testing.T, outputs int) []byte {
t.Helper()
ia := &protoactions.IssueAction{
Version: actions.ProtocolV1,
Issuer: nil,
}
for range outputs {
ia.Outputs = append(ia.Outputs, &protoactions.IssueActionOutput{Token: &protoactions.Token{Owner: []byte("o"), Type: "TYPE", Quantity: "0x1"}})
}
raw, err := proto.Marshal(ia)
require.NoError(t, err)

return raw
}

// marshalTransferAction builds a serialized fabtoken transfer action with the
// given number of inputs and outputs.
func marshalTransferAction(t *testing.T, inputs, outputs int) []byte {
t.Helper()
ta := &protoactions.TransferAction{
Version: actions.ProtocolV1,
}
for range inputs {
ta.Inputs = append(ta.Inputs, &protoactions.TransferActionInput{Input: &protoactions.Token{Owner: []byte("o"), Type: "TYPE", Quantity: "0x1"}})
}
for range outputs {
ta.Outputs = append(ta.Outputs, &protoactions.TransferActionOutput{Token: &protoactions.Token{Owner: []byte("o"), Type: "TYPE", Quantity: "0x1"}})
}
raw, err := proto.Marshal(ta)
require.NoError(t, err)

return raw
}

func issueRequest(raw []byte) *driver.TokenRequest {
return &driver.TokenRequest{
Actions: []*driver.TypedAction{{Type: request.ActionType_ACTION_TYPE_ISSUE, Raw: raw}},
}
}

func transferRequest(raw []byte) *driver.TokenRequest {
return &driver.TokenRequest{
Actions: []*driver.TypedAction{{Type: request.ActionType_ACTION_TYPE_TRANSFER, Raw: raw}},
}
}

// TestActionDeserializer_AppliesConfiguredLimits is a regression test for issue
// #2028: the audit path must enforce the operator-configured resource limits,
// not silently fall back to driver.DefaultResourceLimits(). It exercises a limit
// tighter than the defaults so that a missing SetLimits call (the original bug)
// would let the oversized action through and fail the test.
func TestActionDeserializer_AppliesConfiguredLimits(t *testing.T) {
custom := driver.DefaultResourceLimits()
custom.MaxOutputs = 2
custom.MaxInputs = 2
d := &ActionDeserializer{Limits: custom}

t.Run("issue within limit", func(t *testing.T) {
_, _, err := d.DeserializeActions(issueRequest(marshalIssueAction(t, 2)))
require.NoError(t, err)
})
t.Run("issue exceeds configured limit", func(t *testing.T) {
_, _, err := d.DeserializeActions(issueRequest(marshalIssueAction(t, 3)))
require.ErrorIs(t, err, actions.ErrTooManyOutputs)
})

t.Run("transfer within limit", func(t *testing.T) {
_, _, err := d.DeserializeActions(transferRequest(marshalTransferAction(t, 2, 2)))
require.NoError(t, err)
})
t.Run("transfer exceeds configured input limit", func(t *testing.T) {
_, _, err := d.DeserializeActions(transferRequest(marshalTransferAction(t, 3, 1)))
require.ErrorIs(t, err, actions.ErrTooManyInputs)
})
t.Run("transfer exceeds configured output limit", func(t *testing.T) {
_, _, err := d.DeserializeActions(transferRequest(marshalTransferAction(t, 1, 3)))
require.ErrorIs(t, err, actions.ErrTooManyOutputs)
})
}
11 changes: 10 additions & 1 deletion token/core/fabtoken/v1/auditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type AuditorService struct {
QueryEngine driver.QueryEngine
tracer trace.Tracer

// Limits are the configured resource limits threaded into the auditor so the
// audit path enforces the same policy as the validator (issue #2028).
Limits driver.ResourceLimits

// AuditTokensNumRetries and AuditTokensRetryDelay control how AuditorCheck's
// token lookup tolerates the pending-transaction read-timing race (issue #2105).
// They default to common.DefaultAuditTokensNumRetries / DefaultAuditTokensRetryDelay
Expand All @@ -41,13 +45,17 @@ type AuditorService struct {
// retryConfig sets the audit-token retry/backoff behavior of AuditorCheck; pass
// common.DefaultAuditRetryConfig() for the built-in defaults or
// common.LoadAuditRetryConfig(tmsConfig) to honor the per-TMS configuration file.
//
// limits are the configured resource limits; they are threaded into the auditor
// so the audit path enforces the same policy as the validator (issue #2028).
func NewAuditorService(
logger logging.Logger,
publicParametersManager common.PublicParametersManager[*setup.PublicParams],
deserializer driver.Deserializer,
queryEngine driver.QueryEngine,
tracerProvider trace.TracerProvider,
retryConfig common.AuditRetryConfig,
limits driver.ResourceLimits,
) *AuditorService {
return &AuditorService{
Logger: logger,
Expand All @@ -57,6 +65,7 @@ func NewAuditorService(
tracer: tracerProvider.Tracer("auditor_service", tracing.WithMetricsOpts(tracing.MetricsOpts{})),
AuditTokensNumRetries: retryConfig.NumRetries,
AuditTokensRetryDelay: retryConfig.RetryDelay,
Limits: limits,
}
}

Expand All @@ -79,7 +88,7 @@ func (s *AuditorService) AuditorCheck(ctx context.Context, request *driver.Token
}

pp := s.PublicParametersManager.PublicParams()
auditor := audit.NewAuditor(s.Logger, s.tracer, s.Deserializer, pp, pp.Precision())
auditor := audit.NewAuditor(s.Logger, s.tracer, s.Deserializer, pp, pp.Precision(), s.Limits)
s.Logger.DebugfContext(ctx, "Start auditor check")
err = auditor.Check(
ctx,
Expand Down
2 changes: 1 addition & 1 deletion token/core/fabtoken/v1/auditor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func newAuditEnv(benchmarkCase *benchmark2.Case) (*auditEnv, error) {
queryEngine := &mockQueryEngine{}
tracerProvider := noop.NewTracerProvider()

as := v1.NewAuditorService(logger, publicParamsManager, deserializer, queryEngine, tracerProvider, common.DefaultAuditRetryConfig())
as := v1.NewAuditorService(logger, publicParamsManager, deserializer, queryEngine, tracerProvider, common.DefaultAuditRetryConfig(), driver.DefaultResourceLimits())

// Create test data structures
issueAction := &actions.IssueAction{
Expand Down
2 changes: 1 addition & 1 deletion token/core/fabtoken/v1/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (d *Driver) NewTokenService(tmsID driver.TMSID, publicParams []byte) (drive
tmsConfig,
metrics.NewIssueService(v1.NewIssueService(publicParamsManager, ws, deserializer), metricsProvider),
metrics.NewTransferService(v1.NewTransferService(logger, publicParamsManager, ws, common.NewVaultTokenLoader(qe), deserializer), metricsProvider),
metrics.NewAuditorService(v1.NewAuditorService(logger, publicParamsManager, deserializer, qe, d.tracerProvider, common.LoadAuditRetryConfig(tmsConfig)), metricsProvider),
metrics.NewAuditorService(v1.NewAuditorService(logger, publicParamsManager, deserializer, qe, d.tracerProvider, common.LoadAuditRetryConfig(tmsConfig), limits), metricsProvider),
metrics.NewTokensService(tokensService, metricsProvider),
metrics.NewTokensUpgradeService(&v1.TokensUpgradeService{}, metricsProvider),
authorization,
Expand Down
Loading