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 ¶
- Constants
- Variables
- func Hash(txid string) ([32]byte, error)
- type ChainSource
- type Client
- func (c *Client) Acknowledged(ctx context.Context, host, office string, to []byte, txid string) (bool, error)
- func (c *Client) Ask(ctx context.Context, host string, q Query) ([]lookup.Output, error)
- func (c *Client) Held(ctx context.Context, host string, q Query, txid string) (bool, error)
- func (c *Client) List(ctx context.Context, q Query, to []byte) (*Listing, error)
- func (c *Client) Pages(ctx context.Context, host string, q Query, to []byte) HostAnswer
- func (c *Client) Verify(ctx context.Context, beef []byte, office string, want boxrec.TxKind, to []byte) (*Item, error)
- type HostAnswer
- type Item
- type Listing
- type Message
- type Query
- type Recipient
- type Refusal
- type Terms
Examples ¶
Constants ¶
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 ¶
var ErrHost = errors.New("reader: the host refused the question")
ErrHost is a host that answered with an error: its words travel with it.
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 ¶
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 ¶
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 ¶
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 ¶
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)
}
}
}
Output:
func (*Client) Pages ¶
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.
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) Missing ¶
Missing are, per host that answered, the verified envelopes another host answered and it did not.
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.
type Query ¶
Query is one ls_bbox question: its members, every one a string.
func BoxQuery ¶
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]
type Recipient ¶
Recipient is what a recipient's wallet does for the checks of spec section 4.7: decrypt, and derive the key a payment pays.