From 3e73f118c5e0432b4c0ef781af433694396c7424 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Fri, 25 Apr 2025 12:45:44 -0400 Subject: [PATCH] Updated ocr3 Metadata type to include Encoding and Decoding --- .../consensus/ocr3/types/aggregator.go | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/pkg/capabilities/consensus/ocr3/types/aggregator.go b/pkg/capabilities/consensus/ocr3/types/aggregator.go index 96ba82be0..db53c0239 100644 --- a/pkg/capabilities/consensus/ocr3/types/aggregator.go +++ b/pkg/capabilities/consensus/ocr3/types/aggregator.go @@ -1,6 +1,11 @@ package types import ( + "bytes" + "encoding/binary" + "encoding/hex" + "fmt" + "io" "strings" ocrcommon "github.com/smartcontractkit/libocr/commontypes" @@ -34,6 +39,150 @@ func (m *Metadata) padWorkflowName() { } } +// Encode serializes Metadata in contract order: +// 1B Version, 32B ExecutionID, 4B Timestamp, 4B DONID, 4B DONConfigVersion, +// 32B WorkflowID, 10B WorkflowName, 20B WorkflowOwner, 2B ReportID +func (m Metadata) Encode() ([]byte, error) { + m.padWorkflowName() + buf := new(bytes.Buffer) + + // 1) Version as a single byte + if err := buf.WriteByte(byte(m.Version)); err != nil { + return nil, err + } + + // 2) Helper to decode a hex string and ensure length + writeHex := func(field string, expectedBytes int) error { + s := strings.TrimPrefix(field, "0x") + b, err := hex.DecodeString(s) + if err != nil { + return fmt.Errorf("invalid hex in field: %w", err) + } + if len(b) != expectedBytes { + return fmt.Errorf("wrong length: expected %d bytes, got %d", expectedBytes, len(b)) + } + _, err = buf.Write(b) + return err + } + + // ExecutionID: 32 bytes + if err := writeHex(m.ExecutionID, 32); err != nil { + return nil, fmt.Errorf("ExecutionID: %w", err) + } + + // Timestamp, DONID, DONConfigVersion—all 4‐byte big endian + for _, v := range []uint32{m.Timestamp, m.DONID, m.DONConfigVersion} { + if err := binary.Write(buf, binary.BigEndian, v); err != nil { + return nil, err + } + } + + // WorkflowID: 32 bytes + if err := writeHex(m.WorkflowID, 32); err != nil { + return nil, fmt.Errorf("WorkflowID: %w", err) + } + + // Workflow Name: 10 bytes + if err := writeHex(m.WorkflowName, 10); err != nil { + return nil, fmt.Errorf("WorkflowName: %w", err) + } + + // WorkflowOwner: 20 bytes + if err := writeHex(m.WorkflowOwner, 20); err != nil { + return nil, fmt.Errorf("WorkflowOwner: %w", err) + } + + // ReportID: 2 bytes + if err := writeHex(m.ReportID, 2); err != nil { + return nil, fmt.Errorf("ReportID: %w", err) + } + + return buf.Bytes(), nil +} + +const MetadataLen = 1 + 32 + 4 + 4 + 4 + 32 + 10 + 20 + 2 // =109 + +// Decode parses exactly MetadataLen bytes from raw, returns a Metadata struct +// and any trailing data. +func Decode(raw []byte) (Metadata, []byte, error) { + m := Metadata{} + + if len(raw) < MetadataLen { + return m, nil, fmt.Errorf("metadata: raw too short, want ≥%d, got %d", MetadataLen, len(raw)) + } + + buf := bytes.NewReader(raw[:MetadataLen]) + + // 1) Version (1 byte) + var vb byte + if err := binary.Read(buf, binary.BigEndian, &vb); err != nil { + return m, nil, err + } + m.Version = uint32(vb) + + // helper to read N bytes and hex-decode + readHex := func(n int) (string, error) { + tmp := make([]byte, n) + if _, err := io.ReadFull(buf, tmp); err != nil { + return "", err + } + return hex.EncodeToString(tmp), nil + } + + // 2) ExecutionID (32 bytes hex) + var err error + if m.ExecutionID, err = readHex(32); err != nil { + return m, nil, fmt.Errorf("ExecutionID: %w", err) + } + + // 3) Timestamp, DONID, DONConfigVersion (each 4 bytes BE) + for _, ptr := range []*uint32{&m.Timestamp, &m.DONID, &m.DONConfigVersion} { + if err := binary.Read(buf, binary.BigEndian, ptr); err != nil { + return m, nil, err + } + } + + // 4) WorkflowID (32 bytes hex) + if m.WorkflowID, err = readHex(32); err != nil { + return m, nil, fmt.Errorf("WorkflowID: %w", err) + } + + nameBytes := make([]byte, 10) + if _, err := io.ReadFull(buf, nameBytes); err != nil { + return m, nil, err + } + // hex-encode those 10 bytes into a 20-char string + m.WorkflowName = hex.EncodeToString(nameBytes) + + // 6) WorkflowOwner (20 bytes hex) + if m.WorkflowOwner, err = readHex(20); err != nil { + return m, nil, fmt.Errorf("WorkflowOwner: %w", err) + } + + // 7) ReportID (2 bytes hex) + if m.ReportID, err = readHex(2); err != nil { + return m, nil, fmt.Errorf("ReportID: %w", err) + } + + // strip any stray "0x" prefixes just in case + m.ExecutionID = strings.TrimPrefix(m.ExecutionID, "0x") + m.WorkflowID = strings.TrimPrefix(m.WorkflowID, "0x") + m.WorkflowOwner = strings.TrimPrefix(m.WorkflowOwner, "0x") + m.ReportID = strings.TrimPrefix(m.ReportID, "0x") + + // the rest is payload + tail := raw[MetadataLen:] + return m, tail, nil +} + +func (m Metadata) Length() int { + b, err := m.Encode() + if err != nil { + return 0 + } + return len(b) +} + // Aggregator is the interface that enables a hook to the Outcome() phase of OCR reporting. type Aggregator interface { // Called by the Outcome() phase of OCR reporting.