reader

package
v0.6.3 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package reader asks overlay hosts the ls_bbox questions and checks every answer itself (docs/spec.md sections 7.2, 9 and 13): the carrier's rules and content rules (the admission rules a host applies), SPV of the carrier through its funding parent against the reader's own headers, and the record's office and recipient. A host is trusted for nothing: an answer is verified here or reported as refused, naming the host. An empty answer means only that the host holds nothing that answers.

A reader that asks several hosts compares their answers and reports a difference as such, never merging it silently: a host can withhold an envelope, and only a comparison shows it.

Index

Examples

Constants

View Source
const MaxBEEF = 1 << 20

MaxBEEF is the most a reader takes for one answered carrier: a carrier is its record and its funding tree, and a tree of 1000 outputs makes a carrier of about 70 KB (spec section 12).

Variables

View Source
var ErrHost = errors.New("reader: the host refused the question")

ErrHost is a host that answered with an error: its words travel with it.

View Source
var ErrNoTerms = errors.New("reader: the host serves no terms document, so it charges for nothing")

ErrNoTerms is a host that serves no terms document: it charges for nothing.

Functions

func Hash

func Hash(txid string) ([32]byte, error)

Hash is a display txid as a hash (hash byte order).

Types

type ChainSource

type ChainSource interface {
	MerkleProof(ctx context.Context, txid string) (*transaction.MerklePath, error)
}

ChainSource is a source of a mined transaction's current proof, by its txid: a node's asset API (nodeapi.Asset is one).

type Client

type Client struct {
	// Hosts are the overlay hosts' base URLs, asked in order.
	Hosts []string
	// Headers is the reader's own header source. It is required: an answer
	// is only as good as the headers it is checked against.
	Headers chaintracker.ChainTracker
	// Timeout bounds each request; HTTP, when set, is the client used.
	Timeout time.Duration
	HTTP    *http.Client
	// MaxPages bounds the pages one question reads per host.
	MaxPages int
	// Source, when set, is a chain source asked for a transaction's
	// current proof when the proof a host stored no longer verifies
	// against Headers. Without one, such an answer is refused.
	Source ChainSource
}

Client asks hosts and checks their answers.

func (*Client) Acknowledged

func (c *Client) Acknowledged(ctx context.Context, host, office string, to []byte, txid string) (bool, error)

Acknowledged asks host whether it answers a receipt by to whose acks list the envelope txid, and checks the receipt as Verify does. In unicast such a receipt also confirms that the host took the envelope (spec section 9): an acknowledged envelope is not open, so no box question answers it, and a resent copy is a duplicate that admits nothing.

func (*Client) Ask

func (c *Client) Ask(ctx context.Context, host string, q Query) ([]lookup.Output, error)

Ask asks one host one question and returns the outputs it answered. An answer over limits.MaxAnswer bytes is an error, not read.

func (*Client) Held

func (c *Client) Held(ctx context.Context, host string, q Query, txid string) (bool, error)

Held asks whether host answers a question with an output whose subject is txid: a unicast publisher's confirmation that a host that admitted nothing holds the object (spec section 9).

func (*Client) List

func (c *Client) List(ctx context.Context, q Query, to []byte) (*Listing, error)

Ask asks every host the question, page after page, and compares.

Example

A recipient lists one box on every host, opens each envelope that verified, and hands a payment that passed every check to its wallet. Nothing a host answered is shown that did not verify, and a host that withholds what another answered is reported (Listing.Missing).

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/bsv-blockchain/go-sdk/transaction/chaintracker"
	"github.com/bsv-blockchain/go-sdk/wallet"

	"github.com/lightwebinc/bbox/reader"
)

func main() {
	ctx := context.Background()
	var (
		headers chaintracker.ChainTracker // the recipient's own headers
		w       wallet.Interface          // the recipient's wallet
		me      []byte                    // the recipient's identity key
	)
	c := &reader.Client{Hosts: []string{"https://host.example"}, Headers: headers, Timeout: 15 * time.Second}
	l, err := c.List(ctx, reader.BoxQuery("example_abcdefghij", me, "payment_inbox"), me)
	if err != nil {
		return
	}
	for _, r := range l.Refusals() {
		fmt.Println("refused", r.Host, r.Txid, r.Reason)
	}
	for _, it := range l.Items {
		m := reader.Open(ctx, w, "example", it, headers, time.Now())
		if m.Err != nil || m.Payment == nil || m.PayErr != nil {
			continue
		}
		args, err := m.Internalize("payment received", []string{"payment"})
		if err != nil {
			continue
		}
		if _, err := w.InternalizeAction(ctx, args, "example"); err == nil {
			fmt.Println("internalized", m.Paid(), "sat from", it.Txid)
		}
	}
}

func (*Client) Pages

func (c *Client) Pages(ctx context.Context, host string, q Query, to []byte) HostAnswer

Pages asks one host an envelope class, page after page, until a page is short. office is the question's office; to, when set, the recipient every envelope must be to. What one page and one walk hold is bounded here, whatever a host answers: a page over the contract's is cut, a walk over walkBytes stops, and a host whose full page does not move past the cursor it was asked after ends the walk. Each is reported as Truncated.

func (*Client) Verify

func (c *Client) Verify(ctx context.Context, beef []byte, office string, want boxrec.TxKind, to []byte) (*Item, error)

Verify is the package's Verify against the client's headers, with a stale proof of the carrier's funding tree, or of a sweep, replaced by the chain source's current one.

type HostAnswer

type HostAnswer struct {
	Host    string
	Items   []*Item
	Refused []Refusal
	// Err is set when the host could not be asked.
	Err error
	// Truncated is set when the page bound stopped the reading.
	Truncated bool
}

HostAnswer is what one host answered to one question, all pages.

type Item

type Item struct {
	Txid string
	// Beef is the carrier's BEEF as answered.
	Beef []byte
	Tx   *transaction.Transaction
	// Admission is what the carrier is, by the host's own rules.
	Admission *boxrec.Admission
}

Item is one answered carrier that verified.

func Verify

func Verify(ctx context.Context, beef []byte, office string, want boxrec.TxKind, to []byte, headers chaintracker.ChainTracker) (*Item, error)

Verify checks one answered carrier (spec section 13 steps 1 and 2): the host's rules for office (the carrier and its content), SPV through its funding parent against headers, and, for an envelope, that it is to the recipient when one is named. want is the kind the question answers.

func (*Item) Created

func (it *Item) Created() uint64

Created is the envelope's created, or zero.

func (*Item) Envelope

func (it *Item) Envelope() *boxrec.Envelope

Envelope is the item's envelope record, or nil.

type Listing

type Listing struct {
	Items []*Item
	// By maps each item's txid to the hosts that answered it.
	By      map[string][]string
	Answers []HostAnswer
}

Listing is one question asked of every host: the envelopes that verified, with the hosts that answered each, and what each host refused or lacked.

func (*Listing) Agree

func (l *Listing) Agree() bool

Agree reports whether every host that answered answered the same set.

func (*Listing) Answered

func (l *Listing) Answered() int

Answered is how many hosts answered at all.

func (*Listing) Find

func (l *Listing) Find(txid string) *Item

Find returns the item with txid, if a host answered it.

func (*Listing) Missing

func (l *Listing) Missing() map[string][]string

Missing are, per host that answered, the verified envelopes another host answered and it did not.

func (*Listing) Refusals

func (l *Listing) Refusals() []Refusal

Refusals are every output some host answered that did not verify.

func (*Listing) Refused

func (l *Listing) Refused(txid string) bool

Refused reports whether a host answered an output with subject txid that did not verify.

type Message

type Message struct {
	Item *Item
	// Plain is the plaintext when checks 1 to 3 passed; Err the first of
	// them that refused the message (undecryptable, plaintext-json,
	// plaintext-shape).
	Plain *boxrec.Plaintext
	Err   error
	// Payment is the payment transaction when the plaintext carries one
	// and it passed checks 4 to 9, which is what the wallet internalizes;
	// PayErr the first of them that refused the payment alone.
	Payment *transaction.Transaction
	PayErr  error
}

Message is what a recipient reads from one verified envelope.

func Open

func Open(ctx context.Context, w Recipient, originator string, it *Item, headers chaintracker.ChainTracker, now time.Time) *Message

Open applies the recipient's checks of spec section 4.7 in order to an envelope that verified, at the recipient's time now: decrypt through w (undecryptable), the plaintext's JSON and members (plaintext-json, plaintext-shape), then, only when it carries a payment, the payment's shape, timing, BEEF, outputs and SPV against the recipient's headers. Checks 1 to 3 refuse the message; 4 to 9 refuse only the payment.

func (*Message) Internalize

func (m *Message) Internalize(description string, labels []string) (wallet.InternalizeActionArgs, error)

Internalize is what the recipient hands its wallet's internalizeAction for a payment that passed every check (m.Payment set): the payment as Atomic BEEF and, for each output the plaintext lists, a BRC-29 wallet payment with the remittance its derivation prefix and suffix and the sender's key make. description and labels are the wallet's, as BRC-100 takes them.

func (*Message) Paid

func (m *Message) Paid() uint64

Paid is the satoshis the payment's listed outputs hold.

type Query

type Query map[string]string

Query is one ls_bbox question: its members, every one a string.

func BoxQuery

func BoxQuery(office string, to []byte, box string) Query

BoxQuery is the question for the open envelopes to the recipient to in office: in one box, or in every box when box is empty (spec section 7.2).

Example

The two questions a recipient asks of its office: one box, and one sender's envelopes.

package main

import (
	"fmt"
	"sort"

	"github.com/lightwebinc/bbox/reader"
)

func main() {
	to := make([]byte, 33)
	to[0] = 0x02
	show := func(q reader.Query) {
		keys := make([]string, 0, len(q))
		for k := range q {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		fmt.Println(keys)
	}
	show(reader.BoxQuery("example_abcdefghij", to, "inbox"))
	show(reader.FromQuery("example_abcdefghij", to, "02aa"))
}
Output:
[box office to]
[from office to]

func FromQuery

func FromQuery(office string, to []byte, from string) Query

FromQuery is the question for the open envelopes to the recipient to in office from one sender, its identity key in lowercase hex.

type Recipient

type Recipient interface {
	boxrec.Decrypter
	boxrec.KeyGetter
}

Recipient is what a recipient's wallet does for the checks of spec section 4.7: decrypt, and derive the key a payment pays.

type Refusal

type Refusal struct {
	Host   string
	Txid   string
	Reason string
	Err    error
}

Refusal is an answered output that did not verify.

type Terms

type Terms struct {
	Service string `json:"service"`
	Terms   int    `json:"terms"`
	Classes []struct {
		Class    string `json:"class"`
		Satoshis uint64 `json:"satoshis"`
	} `json:"classes"`
}

Terms is a host's terms document (spec section 7.4).

func FetchTerms

func FetchTerms(ctx context.Context, hc *http.Client, base string) (*Terms, error)

FetchTerms reads <base>/ls_bbox/terms. Members and classes a client does not know are ignored; a free class with a price is reported, and a client never pays for one.

Jump to

Keyboard shortcuts

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