payerreport

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 12, 2025 License: MIT Imports: 23 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

Index

Constants

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

Variables

View Source
var (
	ErrNoActiveNodeIDs             = errors.New("no active node IDs")
	ErrInvalidReportID             = errors.New("invalid report ID")
	ErrOriginatorNodeIDTooLarge    = errors.New("originator node ID is > max int32")
	ErrStartSequenceIDTooLarge     = errors.New("start sequence ID is > max int64")
	ErrEndSequenceIDTooLarge       = errors.New("end sequence ID is > max int64")
	ErrEndMinuteSinceEpochTooLarge = errors.New("end minute since epoch is > max int32")
	ErrNodesCountTooLarge          = errors.New("nodes count is > max int32")
	ErrActiveNodeIDTooLarge        = errors.New("active node ID is > max int32")
	ErrReportNotFound              = errors.New("report not found")
	ErrReportNil                   = errors.New("report is nil")
)
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")
	ErrMessageNotAtMinuteEnd   = errors.New(
		"sequence id is not the last message in the minute",
	)
	ErrMessageAtStartSequenceIDNotFound = errors.New("message at start sequence id not found")
	ErrMessageAtEndSequenceIDNotFound   = errors.New("message at end sequence id not found")
	ErrMerkleRootMismatch               = errors.New("payers merkle root mismatch")
)
View Source
var PAYER_REPORT_DIGEST_TYPE_HASH = common.HexToHash(
	"3737a2cced99bb28fc5aede45aa81d3ce0aa9137c5f417641835d0d71d303346",
)

PAYER_REPORT_DIGEST_TYPE_HASH is the type hash as defined by EIP-712 for structured data hashing and signing. Calculated as the following keccak256 hash:

keccak256("PayerReport(uint32 originatorNodeId,uint64 startSequenceId,uint64 endSequenceId,uint32 endMinuteSinceEpoch,bytes32 payersMerkleRoot,uint32[] nodeIds)")

Reference: https://github.com/xmtp/smart-contracts/blob/main/src/settlement-chain/PayerReportManager.sol#L29

Functions

This section is empty.

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 IPayerReportStore added in v0.5.0

type IPayerReportStore interface {
	CreatePayerReport(
		ctx context.Context,
		report *PayerReport,
		payerEnvelope *envelopes.PayerEnvelope,
	) (*ReportID, error)
	CreateAttestation(
		ctx context.Context,
		attestation *PayerReportAttestation,
		payerEnvelope *envelopes.PayerEnvelope,
	) error
	FetchReport(ctx context.Context, id ReportID) (*PayerReportWithStatus, error)
	FetchReports(ctx context.Context, query *FetchReportsQuery) ([]*PayerReportWithStatus, error)
	StoreSyncedReport(
		ctx context.Context,
		envelope *envelopes.OriginatorEnvelope,
		payerID int32,
		domainSeparator common.Hash,
	) error
	StoreSyncedAttestation(
		ctx context.Context,
		envelope *envelopes.OriginatorEnvelope,
		payerID int32,
	) error
	SetReportAttestationStatus(
		ctx context.Context,
		id ReportID,
		fromStatus []AttestationStatus,
		toStatus AttestationStatus,
	) error
	Queries() *queries.Queries
}

type IPayerReportVerifier added in v0.5.0

type IPayerReportVerifier interface {
	IsValidReport(
		ctx context.Context,
		prevReport *PayerReport,
		newReport *PayerReport,
	) (bool, error)
}

type NodeSignature

type NodeSignature struct {
	NodeID    uint32
	Signature []byte
}

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
}

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(
	log *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(log *zap.Logger, store IPayerReportStore) *PayerReportVerifier

func (*PayerReportVerifier) IsValidReport added in v0.5.0

func (p *PayerReportVerifier) IsValidReport(
	ctx context.Context,
	prevReport *PayerReport,
	newReport *PayerReport,
) (bool, error)
  • Validate 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
}

A FullPayerReport 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(db *sql.DB, log *zap.Logger) *Store

func (*Store) CreateAttestation added in v0.5.0

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

To be 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) Queries added in v0.5.0

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

func (*Store) SetReportAttestationStatus added in v0.5.0

func (s *Store) SetReportAttestationStatus(
	ctx context.Context,
	id ReportID,
	fromStatus []AttestationStatus,
	toStatus AttestationStatus,
) error

func (*Store) StoreAttestation added in v0.5.0

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

Store an attestation in the database

func (*Store) StoreReport added in v0.5.0

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

Store a report in the database. No validations have been performed, and no originator envelope is stored. This function is primarily used for testing

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

Store a report that has been received through a stream from another node

type SubmissionStatus added in v0.5.0

type SubmissionStatus int16

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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