Skip to content
Closed
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
32 changes: 32 additions & 0 deletions token/core/common/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,38 @@ package common
import (
"context"
"encoding/json"
"time"

"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
"github.com/hyperledger-labs/fabric-token-sdk/token/services/logging"
)

// validationTimeKey is the unexported context key used to carry a deterministic
// reference timestamp into the token-request validator (e.g. the Fabric proposal
// timestamp from stub.GetTxTimestamp()).
type validationTimeKey struct{}

// WithValidationTime returns a child context that carries t as the reference
// time for HTLC deadline evaluation. Call this from the chaincode path so that
// all endorsing peers use the same client-supplied proposal timestamp instead of
// each peer's local wall clock.
func WithValidationTime(ctx context.Context, t time.Time) context.Context {
return context.WithValue(ctx, validationTimeKey{}, t)
}

// validationTimeFromContext extracts the reference time injected by
// WithValidationTime. If none was set it falls back to time.Now(), which
// preserves the existing behaviour for non-chaincode callers (local FSC
// validation, unit tests, etc.).
func validationTimeFromContext(ctx context.Context) time.Time {
if t, ok := ctx.Value(validationTimeKey{}).(time.Time); ok && !t.IsZero() {
return t
}

return time.Now()
}

// MetadataCounterID defines the type for metadata counter identifiers.
type MetadataCounterID = string

Expand All @@ -40,6 +66,11 @@ type Context[P driver.PublicParameters, T driver.Input, TA driver.TransferAction
Ledger driver.Ledger
MetadataCounter map[MetadataCounterID]int
Attributes driver.ValidationAttributes
// Now is the reference time used for HTLC deadline evaluation.
// It is populated by VerifyTransfer from the Go context so that the
// chaincode path can supply the deterministic Fabric proposal timestamp
// (stub.GetTxTimestamp()) instead of each peer's local wall clock.
Now time.Time
}

// CountMetadataKey increments the counter for the passed metadata key.
Expand Down Expand Up @@ -285,6 +316,7 @@ func (v *Validator[P, T, TA, IA, DS]) VerifyTransfer(
SignatureProvider: signatureProvider,
MetadataCounter: map[MetadataCounterID]int{},
Attributes: attributes,
Now: validationTimeFromContext(ctx),
}
for _, v := range v.TransferValidators {
if err := v(ctx, context); err != nil {
Expand Down
5 changes: 4 additions & 1 deletion token/core/fabtoken/v1/validator/validator_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ func TransferBalanceValidate(c context.Context, ctx *Context) error {

// TransferHTLCValidate checks the validity of the HTLC scripts, if any
func TransferHTLCValidate(c context.Context, ctx *Context) error {
now := time.Now()
now := ctx.Now
if now.IsZero() {
now = time.Now()
}

for i, in := range ctx.InputTokens {
owner, err := identity.UnmarshalTypedIdentity(in.GetOwner())
Expand Down
5 changes: 4 additions & 1 deletion token/core/zkatdlog/nogh/v1/validator/validator_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ func TransferZKProofValidate(c context.Context, ctx *Context) error {
// TransferHTLCValidate validates the HTLC scripts in the transfer action.
// It ensures that HTLC scripts only transfer ownership of a single token and that the script conditions are met.
func TransferHTLCValidate(c context.Context, ctx *Context) error {
now := time.Now()
now := ctx.Now
if now.IsZero() {
now = time.Now()
}

for i, in := range ctx.InputTokens {
owner, err := identity.UnmarshalTypedIdentity(in.Owner)
Expand Down
14 changes: 12 additions & 2 deletions token/services/network/fabric/tcc/tcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,20 @@ func (cc *TokenChaincode) ProcessRequest(raw []byte, stub shim.ChaincodeStubInte
return shim.Error(err.Error())
}

// Derive a deterministic reference time from the Fabric proposal timestamp.
// stub.GetTxTimestamp() returns the timestamp embedded by the client in the
// signed proposal; it is identical for all endorsing peers processing the
// same transaction, making HTLC deadline evaluation deterministic across the
// endorsing set regardless of local wall-clock skew.
ts, err := stub.GetTxTimestamp()
if err != nil {
return shim.Error("failed to get tx timestamp: " + err.Error())
}
ctx := common.WithValidationTime(context.Background(), ts.AsTime())

// Verify
actions, attributes, err := validator.UnmarshallAndVerifyWithMetadata(
context.Background(),
ctx,
&ledger{stub: stub, keyTranslator: &keys.Translator{}},
token.RequestAnchor(stub.GetTxID()),
raw,
Expand All @@ -244,7 +255,6 @@ func (cc *TokenChaincode) ProcessRequest(raw []byte, stub shim.ChaincodeStubInte

// Write
w := translator.New(stub.GetTxID(), translator.NewRWSetWrapper(&rwsWrapper{stub: stub}, "", stub.GetTxID()), &keys.Translator{})
ctx := context.Background()
for _, action := range actions {
err = w.Write(ctx, action)
if err != nil {
Expand Down
Loading