payerreport

package
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2026 License: MIT Imports: 24 Imported by: 0

README

Payer Reports

Validating A Report

When a new report is picked up by the worker it is validated against the most recent report submitted to the smart contract for the originator.

Validations

The following validations are performed. If any of these fail, the report can be deemed as invalid immediately.

  1. The originatorNodeID in the new report and the previous report match
  2. The startSequenceID of the new report is equal to the endSequenceID of the previous report
  3. The startSequenceID of the new report is less than or equal to its endSequenceID
  4. The payersMerkleRoot and nodesHash are exactly 32 bytes long
  5. The payersMerkleRoot or payersLeafCount specified in the report are different than the values independently calculated by the validator based on the validator's own records of messages sent.
  6. The nodesHash or nodesCount are different than the values independently calculated by the validator based on the validator's latest view of the NodeRegistry smart contract
Errors validating a report

In some cases, the worker may encounter an error while validating a Payer Report. When an error occurs the report is not deemed either valid or invalid. Errors may be retried for up to 48 hours before the report is considered expired.

Some error conditions when evaluating a report:

  1. The message referenced by the startSequenceID of the report is not found.
  2. The message referenced by the endSequenceID of the report is not found.
  3. Any other database error when validating the report
Misbehaviour in reports
Withholding messages from the final minute

The system assumes that the messages used as the startSequenceID and endSequenceID of a report are both the last message processed by the originator node in the calendar minute (according to the originator's clock). Reports are always generated at least one full minute behind the current clock.

When calculating payer spend this assumption is used to query the database in the range minuteFromStartSequenceID+1 -> minuteFromEndSequenceID. A malicious node may choose to exploit this behaviour by selectively withholding messages from synchronization in the final minute in a report period. Once a report is confirmed, they can then sync those messages with the rest of the network and the messages will appear "between reports" and not be counted.

This behaviour can only be detected when the next report is submitted for the originator. Each node keeps track of the last message for each minute. If the last message for the minute referenced in the endSequenceID of the previous report does not match what they have in their database they can submit a misbehaviour report including any messages with a greater sequence ID in the same minute. This proves the misbehaviour and is cause for a node to be removed from the network.

Documentation

Overview

Package payerreport implements the payer report generator and verifier interfaces.

Index

Constants

View Source
const (
	SubmissionPending   SubmissionStatus = iota
	SubmissionSubmitted                  = 1
	SubmissionSettled                    = 2
	SubmissionRejected                   = 3
)
View Source
const (
	AttestationPending  AttestationStatus = iota
	AttestationApproved                   = 1
	AttestationRejected                   = 2
)

Variables

View Source
var (
	ErrActiveNodeIDTooLarge        = errors.New("active node ID is > max int32")
	ErrEndMinuteSinceEpochTooLarge = errors.New("end minute since epoch is > max int32")
	ErrEndSequenceIDTooLarge       = errors.New("end sequence ID is > max int64")
	ErrInvalidReportID             = errors.New("invalid report ID")
	ErrNoActiveNodeIDs             = errors.New("no active node IDs")
	ErrNodesCountTooLarge          = errors.New("nodes count is > max int32")
	ErrOriginatorNodeIDTooLarge    = errors.New("originator node ID is > max int32")
	ErrReportNil                   = errors.New("report is nil")
	ErrReportNotFound              = errors.New("report not found")
	ErrStartSequenceIDTooLarge     = errors.New("start sequence ID is > max int64")
	ErrReportIndexTooLarge         = errors.New("report index is > max int32")
)
View Source
var (
	ErrMismatchOriginator = errors.New(
		"originator id mismatch between old and new report",
	)
	ErrInvalidReportStart = errors.New(
		"report does not start where the previous report ended",
	)
	ErrInvalidSequenceID                = errors.New("invalid sequence id")
	ErrInvalidOriginatorID              = errors.New("originator id is 0")
	ErrNoNodes                          = errors.New("no nodes in report")
	ErrInvalidPayersMerkleRoot          = errors.New("payers merkle root is invalid")
	ErrMessageAtStartSequenceIDNotFound = errors.New("message at start sequence id not found")
	ErrMessageAtEndSequenceIDNotFound   = errors.New("message at end sequence id not found")
)

Functions

func AddReportLogFields added in v1.0.0

func AddReportLogFields(logger *zap.Logger, report *PayerReport) *zap.Logger

func GenerateMerkleTree added in v1.0.0

func GenerateMerkleTree(payers PayerMap) (*merkle.MerkleTree, error)

func ValidateReportIndex added in v1.0.0

func ValidateReportIndex(index *big.Int) (int32, error)

ValidateReportIndex checks if a big.Int PayerReportIndex fits within int32 bounds. Returns the validated int32 value or ErrReportIndexTooLarge if the value is too large.

Types

type AttestationStatus added in v0.5.0

type AttestationStatus int16

type BuildPayerReportParams added in v0.5.0

type BuildPayerReportParams struct {
	OriginatorNodeID    uint32
	StartSequenceID     uint64
	EndSequenceID       uint64
	EndMinuteSinceEpoch uint32
	Payers              map[common.Address]currency.PicoDollar
	NodeIDs             []uint32
	DomainSeparator     common.Hash
}

type FetchReportsQuery added in v0.5.0

type FetchReportsQuery struct {
	SubmissionStatusIn  []SubmissionStatus
	AttestationStatusIn []AttestationStatus
	StartSequenceID     *uint64
	EndSequenceID       *uint64
	CreatedAfter        time.Time
	OriginatorNodeID    *uint32
	PayerReportID       []byte
	MinAttestations     *int32
}

func NewFetchReportsQuery added in v0.5.0

func NewFetchReportsQuery() *FetchReportsQuery

func (*FetchReportsQuery) WithAttestationStatus added in v0.5.0

func (f *FetchReportsQuery) WithAttestationStatus(
	statuses ...AttestationStatus,
) *FetchReportsQuery

func (*FetchReportsQuery) WithCreatedAfter added in v0.5.0

func (f *FetchReportsQuery) WithCreatedAfter(createdAfter time.Time) *FetchReportsQuery

func (*FetchReportsQuery) WithEndSequenceID added in v0.5.0

func (f *FetchReportsQuery) WithEndSequenceID(endSequenceID uint64) *FetchReportsQuery

func (*FetchReportsQuery) WithMinAttestations added in v0.5.0

func (f *FetchReportsQuery) WithMinAttestations(minAttestations int32) *FetchReportsQuery

func (*FetchReportsQuery) WithOriginatorNodeID added in v0.5.0

func (f *FetchReportsQuery) WithOriginatorNodeID(originatorNodeID uint32) *FetchReportsQuery

func (*FetchReportsQuery) WithReportID added in v0.5.0

func (f *FetchReportsQuery) WithReportID(payerReportID ReportID) *FetchReportsQuery

func (*FetchReportsQuery) WithStartSequenceID added in v0.5.0

func (f *FetchReportsQuery) WithStartSequenceID(startSequenceID uint64) *FetchReportsQuery

func (*FetchReportsQuery) WithSubmissionStatus added in v0.5.0

func (f *FetchReportsQuery) WithSubmissionStatus(statuses ...SubmissionStatus) *FetchReportsQuery

type IPayerReportGenerator added in v0.5.0

type IPayerReportGenerator interface {
	GenerateReport(
		ctx context.Context,
		params PayerReportGenerationParams,
	) (*PayerReportWithInputs, error)
}

type IPayerReportReadStore added in v1.1.0

type IPayerReportReadStore interface {
	FetchReport(ctx context.Context, id ReportID) (*PayerReportWithStatus, error)
	FetchReports(ctx context.Context, query *FetchReportsQuery) ([]*PayerReportWithStatus, error)
}

type IPayerReportStore added in v0.5.0

type IPayerReportStore interface {
	IPayerReportReadStore
	IPayerReportWriteStore
}

type IPayerReportVerifier added in v0.5.0

type IPayerReportVerifier interface {
	VerifyReport(
		ctx context.Context,
		prevReport *PayerReport,
		newReport *PayerReport,
	) (VerifyReportResult, error)
	GetPayerMap(
		ctx context.Context,
		report *PayerReport,
	) (PayerMap, error)
}

type IPayerReportWriteStore added in v1.1.0

type IPayerReportWriteStore interface {
	StoreReport(ctx context.Context, report *PayerReport) (int64, error)
	CreatePayerReport(
		ctx context.Context,
		report *PayerReport,
		payerEnvelope *envelopes.PayerEnvelope,
	) (*ReportID, error)
	CreateAttestation(
		ctx context.Context,
		attestation *PayerReportAttestation,
		payerEnvelope *envelopes.PayerEnvelope,
	) error
	StoreSyncedReport(
		ctx context.Context,
		envelope *envelopes.OriginatorEnvelope,
		payerID int32,
		domainSeparator common.Hash,
	) error
	StoreSyncedAttestation(
		ctx context.Context,
		envelope *envelopes.OriginatorEnvelope,
		payerID int32,
	) error
	SetReportSubmitted(ctx context.Context, id ReportID, reportIndex int32) error
	ForceSetReportSubmitted(ctx context.Context, id ReportID, reportIndex int32) error
	SetReportSettled(ctx context.Context, id ReportID) error
	SetReportSubmissionRejected(ctx context.Context, id ReportID) error
	SetReportAttestationApproved(ctx context.Context, id ReportID) error
	SetReportAttestationRejected(ctx context.Context, id ReportID) error
	// NOTE: Having a broad `Queries` object returned makes this part hard to specialize - some code paths could use it in a read capacity but we cannot infer that from here.
	Queries() *queries.Queries
	GetAdvisoryLocker(
		ctx context.Context,
	) (db.ITransactionScopedAdvisoryLocker, error)
	GetLatestSequenceID(ctx context.Context, originatorNodeID int32) (int64, error)
}

type NodeSignature

type NodeSignature struct {
	NodeID    uint32
	Signature []byte
}

type PayerMap added in v1.0.0

type PayerMap map[common.Address]currency.PicoDollar

type PayerReport

type PayerReport struct {
	ID ReportID
	// The Originator Node that the report is about
	OriginatorNodeID uint32
	// The report applies to messages with sequence IDs > StartSequenceID
	StartSequenceID uint64
	// The report applies to messages with sequence IDs <= EndSequenceID
	EndSequenceID uint64
	// The timestamp of the message at EndSequenceID
	EndMinuteSinceEpoch uint32
	// The merkle root of the Payers mapping
	PayersMerkleRoot common.Hash
	// The active node IDs in the report
	ActiveNodeIDs []uint32
	// The index of the report on the blockchain (null if not yet submitted or submission status is not tracked)
	SubmittedReportIndex *uint32
}

func (*PayerReport) ToClientEnvelope added in v0.5.0

func (p *PayerReport) ToClientEnvelope() (*envelopes.ClientEnvelope, error)

func (*PayerReport) ToProto added in v0.4.0

func (p *PayerReport) ToProto() *proto.PayerReport

type PayerReportAttestation

type PayerReportAttestation struct {
	Report        *PayerReport
	NodeSignature NodeSignature
}

func NewPayerReportAttestation added in v0.5.0

func NewPayerReportAttestation(
	report *PayerReport,
	nodeSignature NodeSignature,
) *PayerReportAttestation

func (*PayerReportAttestation) ToClientEnvelope added in v0.5.0

func (a *PayerReportAttestation) ToClientEnvelope() (*envelopes.ClientEnvelope, error)

func (*PayerReportAttestation) ToProto added in v0.5.0

type PayerReportGenerationParams

type PayerReportGenerationParams struct {
	OriginatorID            uint32
	LastReportEndSequenceID uint64
	NumHours                int
}

type PayerReportGenerator added in v0.5.0

type PayerReportGenerator struct {
	// contains filtered or unexported fields
}

func NewPayerReportGenerator added in v0.5.0

func NewPayerReportGenerator(
	logger *zap.Logger,
	queries *queries.Queries,
	registry registry.NodeRegistry,
	domainSeparator common.Hash,
) *PayerReportGenerator

func (*PayerReportGenerator) GenerateReport added in v0.5.0

type PayerReportVerifier added in v0.5.0

type PayerReportVerifier struct {
	// contains filtered or unexported fields
}

func NewPayerReportVerifier added in v0.5.0

func NewPayerReportVerifier(logger *zap.Logger, store IPayerReportStore) *PayerReportVerifier

func (*PayerReportVerifier) GetPayerMap added in v1.0.0

func (p *PayerReportVerifier) GetPayerMap(
	ctx context.Context,
	report *PayerReport,
) (PayerMap, error)

GetPayerMap regenerates the payer map for a given report.

This function queries the database to build the payer map based on the report's sequence ID range and returns the map of payer addresses to their total spend in picoDollars.

  • @param ctx The context.
  • @param report The payer report to build the map for.
  • @return The payer map or an error.

func (*PayerReportVerifier) VerifyReport added in v1.0.0

func (p *PayerReportVerifier) VerifyReport(
	ctx context.Context,
	prevReport *PayerReport,
	newReport *PayerReport,
) (VerifyReportResult, error)

VerifyReport verifies a payer report.

This function checks that the new report is valid and that it is a valid transition from the previous report.

- The previous report is assumed to be valid, and does not get validated again.

  • Will regenerate the payer map and verify that the merkle root is correct. *
  • @param prevReport The previous report.
  • @param newReport The new report.

type PayerReportWithInputs added in v0.5.0

type PayerReportWithInputs struct {
	PayerReport
	// The payers in the report and the number of messages they paid for
	Payers     map[common.Address]currency.PicoDollar
	MerkleTree *merkle.MerkleTree
	NodeIDs    []uint32
}

PayerReportWithInputs is a superset of a PayerReport that includes the payers and node IDs.

func BuildPayerReport added in v0.5.0

func BuildPayerReport(params BuildPayerReportParams) (*PayerReportWithInputs, error)

type PayerReportWithStatus added in v0.5.0

type PayerReportWithStatus struct {
	PayerReport
	AttestationSignatures []NodeSignature
	// Whether the report has been submitted to the blockchain or not
	SubmissionStatus SubmissionStatus
	// Status of the current node's attestation of the report
	AttestationStatus AttestationStatus
	// The timestamp of when the report was inserted into the node's database
	CreatedAt time.Time
}

type ReportID added in v0.5.0

type ReportID [32]byte

func BuildPayerReportID added in v0.5.0

func BuildPayerReportID(
	originatorNodeID uint32,
	startSequenceID uint64,
	endSequenceID uint64,
	endMinuteSinceEpoch uint32,
	payersMerkleRoot common.Hash,
	activeNodeIDs []uint32,
	domainSeparator common.Hash,
) (*ReportID, error)

func (ReportID) String added in v0.5.0

func (r ReportID) String() string

type Store added in v0.5.0

type Store struct {
	// contains filtered or unexported fields
}

func NewStore added in v0.5.0

func NewStore(logger *zap.Logger, db *db.Handler) *Store

func (*Store) CreateAttestation added in v0.5.0

func (s *Store) CreateAttestation(
	ctx context.Context,
	attestation *PayerReportAttestation,
	payerEnvelope *envelopes.PayerEnvelope,
) error

CreateAttestation is called by the attester of a report. Updates the attestation status of the report and writes a staged originator envelope to the database that can be synced to other nodes.

func (*Store) CreatePayerReport added in v0.5.0

func (s *Store) CreatePayerReport(
	ctx context.Context,
	report *PayerReport,
	payerEnvelope *envelopes.PayerEnvelope,
) (*ReportID, error)

func (*Store) FetchReport added in v0.5.0

func (s *Store) FetchReport(ctx context.Context, id ReportID) (*PayerReportWithStatus, error)

func (*Store) FetchReports added in v0.5.0

func (s *Store) FetchReports(
	ctx context.Context,
	query *FetchReportsQuery,
) ([]*PayerReportWithStatus, error)

func (*Store) ForceSetReportSubmitted added in v1.0.0

func (s *Store) ForceSetReportSubmitted(ctx context.Context, id ReportID, reportIndex int32) error

ForceSetReportSubmitted is used to set the report submitted status to submitted, regardless of the previous status. This is a dangerous operation and should only be used in rare cases, when the system has to guarantee consistency between the database and the chain, where the chain is the source of truth.

func (*Store) GetAdvisoryLocker added in v1.0.0

func (s *Store) GetAdvisoryLocker(
	ctx context.Context,
) (db.ITransactionScopedAdvisoryLocker, error)

func (*Store) GetLatestSequenceID added in v1.0.0

func (s *Store) GetLatestSequenceID(ctx context.Context, originatorNodeID int32) (int64, error)

func (*Store) Queries added in v0.5.0

func (s *Store) Queries() *queries.Queries

TODO: This method returns a vague object that can be used for both read and write queries. These can be abstracted away to more specialized functions.

func (*Store) SetReportAttestationApproved added in v0.5.1

func (s *Store) SetReportAttestationApproved(ctx context.Context, id ReportID) error

func (*Store) SetReportAttestationRejected added in v0.5.1

func (s *Store) SetReportAttestationRejected(ctx context.Context, id ReportID) error

func (*Store) SetReportSettled added in v0.5.1

func (s *Store) SetReportSettled(ctx context.Context, id ReportID) error

func (*Store) SetReportSubmissionRejected added in v1.0.0

func (s *Store) SetReportSubmissionRejected(ctx context.Context, id ReportID) error

func (*Store) SetReportSubmitted added in v0.5.1

func (s *Store) SetReportSubmitted(ctx context.Context, id ReportID, reportIndex int32) error

func (*Store) StoreAttestation added in v0.5.0

func (s *Store) StoreAttestation(ctx context.Context, attestation *PayerReportAttestation) error

StoreAttestation stores an attestation in the database.

func (*Store) StoreReport added in v0.5.0

func (s *Store) StoreReport(ctx context.Context, report *PayerReport) (int64, error)

StoreReport stores a report in the database. No validations have been performed, and no originator envelope is stored.

func (*Store) StoreSyncedAttestation added in v0.5.0

func (s *Store) StoreSyncedAttestation(
	ctx context.Context,
	envelope *envelopes.OriginatorEnvelope,
	payerID int32,
) error

func (*Store) StoreSyncedReport added in v0.5.0

func (s *Store) StoreSyncedReport(
	ctx context.Context,
	envelope *envelopes.OriginatorEnvelope,
	payerID int32,
	domainSeparator common.Hash,
) error

StoreSyncedReport stores a report that has been received through a stream from another node.

type SubmissionStatus added in v0.5.0

type SubmissionStatus int16

type VerifyMerkleRootResult added in v1.0.0

type VerifyMerkleRootResult struct {
	IsValid bool
	Reason  string
}

type VerifyReportResult added in v1.0.0

type VerifyReportResult struct {
	IsValid bool
	Reason  string
}

Directories

Path Synopsis
Package workers implements the attestation worker for the payer report.
Package workers implements the attestation worker for the payer report.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL