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
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,11 @@ broken_intra_doc_links = "deny"
[workspace.dependencies]
# SDKs (overarching crates that include all other crates)
blueprint-sdk = { version = "0.2.0-alpha.4", path = "./crates/sdk", default-features = false }
tnt-core-bindings = "0.11.3"
# tnt-core v0.13.0 binds quotes to the requester address (audit Round 2 economic F1).
# Upstream tnt-core PRs #124 and #125 land the change. Bindings 0.13.0 is not yet on
# crates.io, so we pin to the `main` branch in git until the publish lands. Flip this
# back to a versioned crates.io dep (`"0.13.0"`) once published.
tnt-core-bindings = { git = "https://github.com/tangle-network/tnt-core", branch = "main" }

# Job system
blueprint-core = { version = "0.2.0-alpha.3", path = "crates/core", default-features = false }
Expand Down
24 changes: 18 additions & 6 deletions crates/pricing-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ Price is computed from the operator's resource pricing config (`default_pricing.
Used with `submitJobFromQuote()` on the Tangle contract. The operator quotes a specific price for a single job execution.

```
Consumer → GetJobPrice(service_id, job_index) → Operator
Operator → signs JobQuoteDetails{serviceId, jobIndex, price, timestamp, expiry}
Consumer → GetJobPrice(service_id, job_index, requester) → Operator
Operator → signs JobQuoteDetails{requester, serviceId, jobIndex, price, timestamp, expiry, confidentiality}
Consumer → submitJobFromQuote(serviceId, jobIndex, inputs, [signedQuotes])
```

Since tnt-core v0.13.0, every quote is bound to a specific `requester`
address (audit Round 2 economic F1, PRs #124 and #125). The on-chain
verifier rejects `requester == address(0)` and rejects any submitter
whose `msg.sender` doesn't match. The gRPC layer enforces non-zero
`requester` at the boundary.

Price is looked up from the operator's per-job pricing config: a `(service_id, job_index) → price_in_wei` map.

## Pricing Configuration
Expand Down Expand Up @@ -154,28 +160,34 @@ chainId: <chain_id>
verifyingContract: <tangle_proxy_address>
```

**Service quotes** use `QUOTE_TYPEHASH`:
**Service quotes** use `QUOTE_TYPEHASH` (tnt-core v0.13.0+):
```
QuoteDetails(uint64 blueprintId,uint64 ttlBlocks,uint256 totalCost,uint64 timestamp,uint64 expiry,AssetSecurityCommitment[] securityCommitments,ResourceCommitment[] resourceCommitments)
QuoteDetails(address requester,uint64 blueprintId,uint64 ttlBlocks,uint256 totalCost,uint64 timestamp,uint64 expiry,uint8 confidentiality,AssetSecurityCommitment[] securityCommitments,ResourceCommitment[] resourceCommitments)
```

**Job quotes** use `JOB_QUOTE_TYPEHASH`:
**Job quotes** use `JOB_QUOTE_TYPEHASH` (tnt-core v0.13.0+):
```
JobQuoteDetails(uint64 serviceId,uint8 jobIndex,uint256 price,uint64 timestamp,uint64 expiry)
JobQuoteDetails(address requester,uint64 serviceId,uint8 jobIndex,uint256 price,uint64 timestamp,uint64 expiry,uint8 confidentiality)
```

`requester` MUST be non-zero — wildcard quotes are rejected by the
on-chain verifier. The address is bound into the EIP-712 signature so a
quote can only be redeemed by the buyer it was issued to.

For standalone signing without the full pricing engine, use `JobQuoteSigner` from `blueprint-tangle-extra`:

```rust
use blueprint_tangle_extra::job_quote::{JobQuoteSigner, JobQuoteDetails, QuoteSigningDomain};

let signer = JobQuoteSigner::new(keypair, QuoteSigningDomain { chain_id, verifying_contract });
let signed = signer.sign(&JobQuoteDetails {
requester: buyer_address, // MUST be non-zero
service_id: 1,
job_index: 7,
price: U256::from(250_000_000_000_000_000u64),
timestamp: now,
expiry: now + 3600,
confidentiality: 0,
});
```

Expand Down
18 changes: 17 additions & 1 deletion crates/pricing-engine/proto/pricing.proto
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ message GetPriceRequest {
PricingModelHint pricing_model = 7;
// Whether the customer requires TEE-attested service execution.
bool require_tee = 8;
// Requester address (20 bytes). Must be non-zero — wildcard quotes are
// rejected by the on-chain verifier in tnt-core v0.13.0+. Bound into the
// EIP-712 signature so a quote can only be redeemed by this address.
bytes requester = 9;
}

// Resource requirement for a specific resource type
Expand Down Expand Up @@ -119,6 +123,10 @@ message QuoteDetails {
repeated ResourcePricing resources = 6;
// Security commitments for assets
repeated AssetSecurityCommitment security_commitments = 7;
// Requester address (20 bytes). Must be non-zero — wildcard quotes are
// rejected by the on-chain verifier in tnt-core v0.13.0+. Bound into the
// EIP-712 signature so a quote can only be redeemed by this address.
bytes requester = 8;
}

// Pricing for a specific resource type
Expand Down Expand Up @@ -147,6 +155,10 @@ message GetJobPriceRequest {
uint64 challenge_timestamp = 4;
// Whether the customer requires TEE-attested service execution.
bool require_tee = 5;
// Requester address (20 bytes). Must be non-zero — wildcard quotes are
// rejected by the on-chain verifier in tnt-core v0.13.0+. Bound into the
// EIP-712 signature so a quote can only be redeemed by this address.
bytes requester = 6;
}

// Response message for GetJobPrice RPC
Expand Down Expand Up @@ -189,7 +201,7 @@ message SettlementOption {
string scheme = 6;
}

// Per-job quote details matching tnt-core Types.JobQuoteDetails
// Per-job quote details matching tnt-core Types.JobQuoteDetails (v0.13.0+)
message JobQuoteDetails {
// The service instance ID
uint64 service_id = 1;
Expand All @@ -205,4 +217,8 @@ message JobQuoteDetails {
// Bound into the EIP-712 signature to prevent replay of
// a non-TEE quote for a TEE-required service.
uint32 confidentiality = 6;
// Requester address (20 bytes). Must be non-zero — wildcard quotes are
// rejected by the on-chain verifier in tnt-core v0.13.0+. Bound into the
// EIP-712 signature so a quote can only be redeemed by this address.
bytes requester = 7;
}
Loading
Loading