Skip to content

Commit 00061b9

Browse files
committed
compute workload id from quote
1 parent 8cb48a0 commit 00061b9

File tree

6 files changed

+145
-31
lines changed

6 files changed

+145
-31
lines changed

config/l2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type L2 struct {
3333
MonitorTxReceipts bool `yaml:"monitor_tx_receipts"`
3434
MonitorWalletAddresses map[string]string `yaml:"monitor_wallet_addresses"`
3535

36-
ProbeTx *ProbeTx `yaml:"probe"`
36+
ProbeTx *ProbeTx `yaml:"probe"`
3737
}
3838

3939
const (

contracts/types.go

Lines changed: 0 additions & 20 deletions
This file was deleted.

metrics/exports.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ var (
1818
FlashblocksLandedCount otelapi.Int64Gauge
1919
FlashblocksMissedCount otelapi.Int64Gauge
2020

21-
FlashtestationsLandedCount otelapi.Int64Gauge
22-
FlashtestationsMissedCount otelapi.Int64Gauge
21+
FlashtestationsLandedCount otelapi.Int64Gauge
22+
FlashtestationsMissedCount otelapi.Int64Gauge
2323
RegisteredFlashtestationsCount otelapi.Int64Gauge
2424

2525
ReorgsCount otelapi.Int64Counter

metrics/metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func setupFlashtestationsMissedCount(ctx context.Context, _ *config.ProbeTx) err
155155
return nil
156156
}
157157

158-
func setupRegisteredFlashtestationsCount(ctx context.Context, _*config.ProbeTx) error {
158+
func setupRegisteredFlashtestationsCount(ctx context.Context, _ *config.ProbeTx) error {
159159
m, err := meter.Int64Gauge("registered_flashtestations_count",
160160
otelapi.WithDescription("registered flashtestations count"),
161161
)

server/l2.go

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/ecdsa"
66
"encoding/binary"
7+
"encoding/hex"
78
"encoding/json"
89
"errors"
910
"fmt"
@@ -23,6 +24,7 @@ import (
2324

2425
"go.uber.org/zap"
2526

27+
"github.com/ethereum/go-ethereum/accounts/abi"
2628
ethcommon "github.com/ethereum/go-ethereum/common"
2729
ethtypes "github.com/ethereum/go-ethereum/core/types"
2830
"github.com/ethereum/go-ethereum/crypto"
@@ -1109,7 +1111,7 @@ func (l2 *L2) checkAndResetProbeTxNonce(ctx context.Context) {
11091111
func (l2 *L2) handleRegistrationTx(ctx context.Context, txHash ethcommon.Hash) {
11101112
l := logutils.LoggerFromContext(ctx)
11111113

1112-
teeAddress, err := l2.getTEEAddressFromTx(ctx, txHash)
1114+
teeAddress, rawQuote, err := l2.getTEEAddressAndQuoteFromTx(ctx, txHash)
11131115
if err != nil {
11141116
l.Warn("Failed to get register flashtestations transaction receipt",
11151117
zap.Error(err),
@@ -1118,32 +1120,74 @@ func (l2 *L2) handleRegistrationTx(ctx context.Context, txHash ethcommon.Hash) {
11181120
return
11191121
}
11201122

1123+
workloadId, err := ComputeWorkloadID(rawQuote)
1124+
if err != nil {
1125+
l.Warn("Failed to compute workload id",
1126+
zap.Error(err),
1127+
zap.String("tx", txHash.Hex()),
1128+
)
1129+
return
1130+
}
1131+
11211132
metrics.RegisteredFlashtestationsCount.Record(ctx, 1, otelapi.WithAttributes(
11221133
attribute.KeyValue{Key: "kind", Value: attribute.StringValue("l2")},
11231134
attribute.KeyValue{Key: "network_id", Value: attribute.Int64Value(l2.chainID.Int64())},
11241135
attribute.KeyValue{Key: "tee_address", Value: attribute.StringValue(teeAddress.Hex())},
1136+
attribute.KeyValue{Key: "workload_id", Value: attribute.StringValue(hex.EncodeToString(workloadId[:]))},
11251137
))
11261138

1139+
l.Info("TEE service registered",
1140+
zap.String("teeAddress", teeAddress.Hex()),
1141+
zap.String("workloadId", hex.EncodeToString(workloadId[:])),
1142+
)
1143+
11271144
return
11281145
}
11291146

1130-
// Extract TEE address from TEEServiceRegistered event
1131-
func (l2 *L2) getTEEAddressFromTx(ctx context.Context, txHash ethcommon.Hash) (ethcommon.Address, error) {
1147+
// Extract TEE address and raw quote from TEEServiceRegistered event
1148+
func (l2 *L2) getTEEAddressAndQuoteFromTx(ctx context.Context, txHash ethcommon.Hash) (ethcommon.Address, []byte, error) {
11321149
receipt, err := l2.rpc.TransactionReceipt(ctx, txHash)
11331150
if err != nil {
1134-
return ethcommon.Address{}, err
1151+
return ethcommon.Address{}, nil, err
11351152
}
11361153

11371154
if receipt.Status == ethtypes.ReceiptStatusFailed {
1138-
return ethcommon.Address{}, fmt.Errorf("Register tee transaction did not succeeed %s", txHash.Hex())
1155+
return ethcommon.Address{}, nil, fmt.Errorf("Register tee transaction did not succeeed %s", txHash.Hex())
1156+
}
1157+
1158+
// Define the event arguments for decoding (non-indexed parameters only)
1159+
// event TEEServiceRegistered(address indexed teeAddress, bytes rawQuote, bool alreadyExists);
1160+
bytesType, _ := abi.NewType("bytes", "", nil)
1161+
boolType, _ := abi.NewType("bool", "", nil)
1162+
1163+
eventABI := abi.Arguments{
1164+
{Type: bytesType}, // rawQuote
1165+
{Type: boolType}, // alreadyExists
11391166
}
11401167

11411168
for _, log := range receipt.Logs {
11421169
if len(log.Topics) > 1 && log.Topics[0] == l2.flashtestationsRegistryEventSignature {
11431170
// TEE address is in Topics[1] (indexed parameter)
1144-
return ethcommon.BytesToAddress(log.Topics[1].Bytes()), nil
1171+
teeAddress := ethcommon.BytesToAddress(log.Topics[1].Bytes())
1172+
1173+
// Decode the data field to get rawQuote and alreadyExists
1174+
decoded, err := eventABI.Unpack(log.Data)
1175+
if err != nil {
1176+
return ethcommon.Address{}, nil, fmt.Errorf("failed to decode event data: %w", err)
1177+
}
1178+
1179+
if len(decoded) < 2 {
1180+
return ethcommon.Address{}, nil, fmt.Errorf("unexpected decoded data length: %d", len(decoded))
1181+
}
1182+
1183+
rawQuote, ok := decoded[0].([]byte)
1184+
if !ok {
1185+
return ethcommon.Address{}, nil, fmt.Errorf("failed to type assert rawQuote")
1186+
}
1187+
1188+
return teeAddress, rawQuote, nil
11451189
}
11461190
}
11471191

1148-
return ethcommon.Address{}, fmt.Errorf("TEEServiceRegistered event not found in tx %s", txHash.Hex())
1192+
return ethcommon.Address{}, nil, fmt.Errorf("TEEServiceRegistered event not found in tx %s", txHash.Hex())
11491193
}

server/workload_id.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package server
2+
3+
// Implementation based on https://github.com/flashbots/flashtestations/tree/7cc7f68492fe672a823dd2dead649793aac1f216
4+
import (
5+
"encoding/binary"
6+
"fmt"
7+
8+
"golang.org/x/crypto/sha3"
9+
)
10+
11+
const (
12+
// Raw TDX v4 quote structure constants
13+
// Raw quote has a 48-byte header before the TD10ReportBody
14+
HEADER_LENGTH = 48
15+
TD_REPORT10_LENGTH = 584
16+
17+
// TDX workload constants
18+
TD_XFAM_FPU = 0x0000000000000001
19+
TD_XFAM_SSE = 0x0000000000000002
20+
TD_TDATTRS_VE_DISABLED = 0x0000000010000000
21+
TD_TDATTRS_PKS = 0x0000000040000000
22+
TD_TDATTRS_KL = 0x0000000080000000
23+
)
24+
25+
// ComputeWorkloadID computes the workload ID from Automata's serialized verifier output
26+
// This corresponds to QuoteParser.parseV4Quote in Solidity
27+
// The workload ID uniquely identifies a TEE workload based on its measurement registers
28+
func ComputeWorkloadID(rawQuote []byte) ([32]byte, error) {
29+
var workloadID [32]byte
30+
31+
// Validate quote length
32+
if len(rawQuote) < HEADER_LENGTH+TD_REPORT10_LENGTH {
33+
return workloadID, fmt.Errorf("invalid quote length: %d, expected at least %d",
34+
len(rawQuote), HEADER_LENGTH+TD_REPORT10_LENGTH)
35+
}
36+
37+
// Skip the 48-byte header to get to the TD10ReportBody
38+
reportBody := rawQuote[HEADER_LENGTH:]
39+
40+
// Extract fields exactly as parseRawReportBody does in Solidity
41+
// Using hardcoded offsets to match Solidity implementation exactly
42+
mrTd := reportBody[136 : 136+48]
43+
rtMr0 := reportBody[328 : 328+48]
44+
rtMr1 := reportBody[376 : 376+48]
45+
rtMr2 := reportBody[424 : 424+48]
46+
rtMr3 := reportBody[472 : 472+48]
47+
mrConfigId := reportBody[184 : 184+48]
48+
49+
// Extract xFAM and tdAttributes (8 bytes each)
50+
// In Solidity, bytes8 is treated as big-endian for bitwise operations
51+
xfam := binary.BigEndian.Uint64(reportBody[128 : 128+8])
52+
tdAttributes := binary.BigEndian.Uint64(reportBody[120 : 120+8])
53+
54+
// Apply transformations as per the Solidity implementation
55+
// expectedXfamBits = TD_XFAM_FPU | TD_XFAM_SSE
56+
expectedXfamBits := uint64(TD_XFAM_FPU | TD_XFAM_SSE)
57+
58+
// ignoredTdAttributesBitmask = TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL
59+
ignoredTdAttributesBitmask := uint64(TD_TDATTRS_VE_DISABLED | TD_TDATTRS_PKS | TD_TDATTRS_KL)
60+
61+
// Transform xFAM: xFAM ^ expectedXfamBits
62+
transformedXfam := xfam ^ expectedXfamBits
63+
64+
// Transform tdAttributes: tdAttributes & ~ignoredTdAttributesBitmask
65+
transformedTdAttributes := tdAttributes & ^ignoredTdAttributesBitmask
66+
67+
// Convert transformed values back to bytes (big-endian, to match Solidity bytes8)
68+
xfamBytes := make([]byte, 8)
69+
tdAttributesBytes := make([]byte, 8)
70+
binary.BigEndian.PutUint64(xfamBytes, transformedXfam)
71+
binary.BigEndian.PutUint64(tdAttributesBytes, transformedTdAttributes)
72+
73+
// Concatenate all fields
74+
var concatenated []byte
75+
concatenated = append(concatenated, mrTd...)
76+
concatenated = append(concatenated, rtMr0...)
77+
concatenated = append(concatenated, rtMr1...)
78+
concatenated = append(concatenated, rtMr2...)
79+
concatenated = append(concatenated, rtMr3...)
80+
concatenated = append(concatenated, mrConfigId...)
81+
concatenated = append(concatenated, xfamBytes...)
82+
concatenated = append(concatenated, tdAttributesBytes...)
83+
84+
// Compute keccak256 hash
85+
hash := sha3.NewLegacyKeccak256()
86+
hash.Write(concatenated)
87+
copy(workloadID[:], hash.Sum(nil))
88+
89+
return workloadID, nil
90+
}

0 commit comments

Comments
 (0)