send

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: 30 Imported by: 0

Documentation

Overview

Package send is a bbox publisher: it seals envelopes, acknowledges them with receipts and retracts them with sweeps (docs/spec.md sections 3 to 6 and 9), from one identity's home.

It is built on bcommon's producer package. A Payer pays for funding trees from the home's coin pool, and Trees mints them, with Ahead, so the next tree is minted before the current one runs out. Payer.Async is on: a tree is handed to the settlement leg and recorded at once, so a command never waits for a block to mint ahead, and a carrier, which spends only a mined tree's output and carries the tree with its proof (spec section 6.3), waits for the proof only when it needs one. A tree is recorded with its fee coin before it reaches the leg (Trees.Prepare), and a command that finds the record of a tree never adopted settles it before it spends (RecoverTrees). A funding tree is not published to an office: every carrier carries its own, so Trees publishes through a facade that sends nothing.

Every object is persisted in the home's state, with the funding output it spends marked used, before it is published: a run that stops part way publishes those same bytes again (Resume), and never a second carrier on the same funding output, which a host would count as superseded.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrLocked = state.ErrLocked

ErrLocked is a home whose lock another process holds.

View Source
var ErrSweepInFlight = errors.New("a sweep is still in flight; the next drop finishes it first")

ErrSweepInFlight is a new sweep asked for while one is still in flight.

View Source
var Params = boxrec.Params()

Params are the carrier parameters of every bbox carrier.

View Source
var Profile = bwallet.Profile{
	FundProtocol:   boxrec.Protocol,
	FundKeyID:      boxrec.KeyFund,
	FundBasket:     "bbox fund",
	Version:        "bbox-1",
	LegacyPoolFile: "bbox-pool.json",
}

Profile is the embedded wallet's profile: the fund key under [1, "bbox message"] key id fund, and the registered basket.

View Source
var Retries = limits.Retries

Retries are the waits between the tries of a submission to the facade.

Functions

func CollectChange

func CollectChange(ctx context.Context, pool *bwallet.Pool, proofs nodeapi.ProofSource) int

CollectChange gives every unproven coin in the pool its proof, once its transaction has mined: change from a payment the payee broadcast, or from anything else published before it mined.

func FeeError

func FeeError(err error) error

FeeError words the library's refusals to find a fee input.

func Journal

func Journal(st *state.State, pool *bwallet.Pool, carried []bwallet.Output)

Journal makes every save of st record the pool, with the coins carried over from a reconciliation that could not settle them.

func LockHome

func LockHome(dir string) (unlock func(), err error)

LockHome takes a home's lock: one process that spends from its pool or writes its state at a time. It is taken before the wallet is opened, since the wallet's pool is read when it is opened and written back whole. The returned function releases it.

Example

A home is locked before its wallet is opened, one process at a time, and its state is started empty the first time.

package main

import (
	"errors"
	"fmt"
	"os"

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

func main() {
	dir, err := os.MkdirTemp("", "bbox-home")
	if err != nil {
		panic(err)
	}
	defer os.RemoveAll(dir)

	unlock, err := send.LockHome(dir)
	if err != nil {
		panic(err)
	}
	defer unlock()
	_, err = send.LockHome(dir)
	fmt.Println(errors.Is(err, send.ErrLocked))

	st, err := send.LoadState(dir, "02aa")
	if err != nil {
		panic(err)
	}
	fmt.Println(st.Identity, len(st.Sent))
}
Output:
true
02aa 0

func Minimal

Minimal is mp cut to the proof of txid alone (boxrec.MinimalPath).

func Reconcile

func Reconcile(ctx context.Context, st *state.State, pool *bwallet.Pool, spends nodeapi.SpendSource, note func(string, ...any)) []bwallet.Output

Reconcile settles what a run that stopped left in the journal. A coin the journal holds that the pool no longer does was taken; when no transaction the home records spends it, the run stopped before it recorded one. The node is asked: a coin it shows unspent goes back in the pool; one it shows spent is spent by a transaction this home did not record, and is reported; one it cannot answer for is carried over to the next command.

func Topic

func Topic(office string) string

Topic is an office's topic.

func Unjournal

func Unjournal(st *state.State)

Unjournal ends the journal: the command ended, so the pool's file and the state agree, and nothing is recorded as being taken.

Types

type Engine

type Engine struct {
	St     *state.State
	Signer *bwallet.Signer
	Pool   *bwallet.Pool
	Legs   Legs
	Opts   Options
	// Now is the clock records are stamped with.
	Now func() time.Time
	// Note receives progress lines.
	Note    func(format string, args ...any)
	Verbose bool
	// contains filtered or unexported fields
}

Engine is one publisher run over one home.

func New

func New(st *state.State, signer *bwallet.Signer, pool *bwallet.Pool, legs Legs, o Options) (*Engine, error)

New builds an engine over a home's state.

func (*Engine) Close

func (e *Engine) Close(ctx context.Context) error

Close collects a tree still being minted ahead, so its change reaches the pool, and records every tree the library holds and never adopted in the state's Ahead list, so a sweep finds it.

func (*Engine) Envelope

func (e *Engine) Envelope(ctx context.Context, l Letter) (sent *state.Sent, err error)

Envelope seals l, mints its carrier, persists it and publishes it. The record is written to the state before anything leaves the machine.

Example

A direct message with an application's member, sent to the recipient's office and box over the plane: the home is locked, its wallet opened, the engine started (it finishes what an earlier run left), and the envelope sealed, persisted and published.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/lightwebinc/bcommon/bwallet"
	"github.com/lightwebinc/bcommon/headers"
	"github.com/lightwebinc/bcommon/mint"
	"github.com/lightwebinc/bcommon/nodeapi"
	"github.com/lightwebinc/bcommon/publish"

	"github.com/lightwebinc/bbox/boxrec"
	"github.com/lightwebinc/bbox/send"
)

func main() {
	ctx := context.Background()
	dir := "/path/to/home"
	var to []byte // the recipient's identity key

	unlock, err := send.LockHome(dir)
	if err != nil {
		return
	}
	defer unlock()
	w, err := bwallet.Open(dir, send.Profile)
	if err != nil {
		return
	}
	st, err := send.LoadState(dir, w.Signer().IdentityHex())
	if err != nil {
		return
	}
	// No node: WhatsOnChain headers and chain view, GorillaPool's arcade.
	hc := headers.New("woc:main")
	chain, err := nodeapi.ParseChain("woc:main", nodeapi.ChainOptions{Headers: hc})
	if err != nil {
		return
	}
	settler, arcade, err := publish.ParseSettler("arcade:main", publish.SettleOptions{Spends: chain})
	if err != nil {
		return
	}
	legs := send.Legs{
		Settler: settler,
		Arcade:  arcade,
		Chain:   chain,
		Headers: hc,
		Facade:  &publish.Facade{Base: "https://host.example"},
	}
	eng, err := send.New(st, w.Signer(), w.Pool, legs, send.Options{TreeCount: 32, TreeSats: 1, Ahead: 4,
		Fees: mint.DefaultFees, ObjectBound: 1 << 20, Poll: 2 * time.Second, Wait: 10 * time.Minute})
	if err != nil {
		return
	}
	defer func() { _ = eng.Close(context.WithoutCancel(ctx)) }()
	if err := eng.Start(ctx); err != nil {
		return
	}

	app := &boxrec.Object{}
	app.Set("v", boxrec.Int(1))
	doc := &boxrec.Object{}
	doc.Set(boxrec.PlainBody, "hello")
	doc.Set("app", app)
	plain, err := boxrec.EncodePlaintext(doc)
	if err != nil {
		return
	}
	now := uint64(time.Now().Unix()) //nolint:gosec // a clock after 1970
	sent, err := eng.Envelope(ctx, send.Letter{Office: "example_abcdefghij", To: to, Box: "inbox", Created: now, Plaintext: plain})
	if err != nil {
		return
	}
	fmt.Println("sent", sent.Txid)
}

func (*Engine) FinishSweep

func (e *Engine) FinishSweep(ctx context.Context) error

FinishSweep takes the sweep in flight, if any, through settlement, its proof and its publication. A sweep the network definitively refuses is marked failed, its fee coin given back when the node shows it unspent, and a *SweepRefusedError returned: it is no longer in flight, so a later drop goes on. Any other failure leaves it in flight, to be tried again.

func (*Engine) NewPayer

func (e *Engine) NewPayer() *producer.Payer

NewPayer is a Payer over the engine's pool and legs.

func (*Engine) Payment

func (e *Engine) Payment(ctx context.Context, toHex string, sats uint64) (*Pay, error)

Payment builds a BRC-29 payment of sats to the identity toHex from the pool, unbroadcast (spec section 10): the sender never broadcasts it; the recipient internalizes it, which broadcasts it. It returns the plaintext member and the transaction. The fee coin is spent, and the change is held in the pool until the payment mines.

func (*Engine) Receipt

func (e *Engine) Receipt(ctx context.Context, office string, txids []string) (*state.Receipt, error)

Receipt acknowledges envelopes in office (display txids, at most 64), mints the receipt carrier, persists it and publishes it.

func (*Engine) RecoverTrees

func (e *Engine) RecoverTrees(ctx context.Context) error

RecoverTrees settles every tree a run signed for a coin and did not adopt, before anything is spent:

  • a tree the chain holds is adopted, or held while the current tree has outputs left, and its change is taken. Any number are held, in the order of their records, and the switch takes them in that order. A held tree is recorded among the trees minted ahead, and its record stays until the switch adopts it;
  • a tree with no proof whose coin another transaction spent is gone, whether the node does not know it or still serves it after it lost a double spend, and its record is dropped;
  • a tree the node does not know whose coin is unspent never reached the chain as far as the node can say now. The coin goes back to the pool, and the record is kept for one more command: a tree handed to the leg an instant before the run stopped can reach the node later. The second such answer drops the record;
  • a node that cannot answer decides nothing: the record stays, the next command asks again, and this one goes on. So does a node that serves an unproven tree and cannot say who spent its coin: trees are settled without waiting for a block, and none is adopted on that alone.

A coin the pool holds although the chain shows it spent (it was put back before the tree landed) is taken out of the pool by the library, which says so. Only a failure to save the state is returned.

func (*Engine) Resume

func (e *Engine) Resume(ctx context.Context) error

Resume publishes every object a previous run persisted: the same bytes, so a host that already holds one answers a duplicate. Every object is tried; one that cannot be published stays in the outbox, and the first failure is returned.

func (*Engine) Retract

func (e *Engine) Retract(ctx context.Context, txid string, vouts []uint32, offices []string) (*state.Sweep, error)

Retract builds and persists one sweep of vouts of the funding tree txid, with its fee from the pool, then settles and publishes it. offices are the offices the carriers it retracts are in.

func (*Engine) Start

func (e *Engine) Start(ctx context.Context) error

Start reads the chain tip for coinbase maturity, collects the proofs of change the pool holds back, and finishes whatever a previous run persisted and did not finish: a sweep in flight, and every object not confirmed published. Each is tried whatever became of the one before it: a sweep that cannot finish does not keep an envelope from the hosts. The first failure is returned; the others are noted.

func (*Engine) TreeOf

func (e *Engine) TreeOf(txid string) (*funding.Tree, bool)

TreeOf finds a funding tree the home recorded.

func (*Engine) Tx

func (e *Engine) Tx(txid string) (*transaction.Transaction, error)

Tx is a funding tree the home keeps, rebuilt.

type Legs

type Legs struct {
	Settler publish.Settler
	Chain   nodeapi.Chain
	Arcade  *publish.Arcade
	Facade  *publish.Facade
	Hosts   *unicast.Set
	Direct  *unicast.Set
	Reader  *reader.Client
	Headers chaintracker.ChainTracker
}

Legs are the endpoints a publisher talks to. Objects go to exactly one of Facade (mode plane: one submit, which the plane delivers to every subscribed host) and Hosts (mode unicast: one submit to each host). In mode plane, Direct are the other hosts a sweep is also submitted to, since a host off the plane learns of a sweep only by receiving it. Reader confirms, by lookup, a host that admitted nothing, and on the plane that the hosts named hold what the facade took. Headers are the publisher's own block headers: the chain tip, and a kept proof a reorganization left stale is replaced by the chain's current one before it is used. Chain is the chain view (a node, or WhatsOnChain): transactions, proofs and spends.

type Letter

type Letter struct {
	Office  string
	To      []byte
	Box     string
	Created uint64
	Expires uint64
	// Plaintext is the canonical plaintext (spec section 4.5).
	Plaintext []byte
	// Pay is the payment it carries, already in Plaintext.
	Pay *Pay
}

Letter is one envelope before it is sealed.

type Options

type Options struct {
	// TreeCount and TreeSats size the funding trees; Ahead is how few
	// outputs a tree has left when the next is minted ahead.
	TreeCount int
	TreeSats  uint64
	Ahead     uint32
	// Fees is the fee policy of trees, sweeps and payments.
	Fees mint.Fees
	// ObjectBound is the plane's object bound, in bytes.
	ObjectBound int
	// Poll paces proof waits and Wait bounds one.
	Poll time.Duration
	Wait time.Duration
	// PlaneNeed is, in mode plane, how many of the hosts named must answer
	// a carrier by lookup for it to count as published; zero is all of
	// them. A sweep always needs every host.
	PlaneNeed int
}

Options are a publisher's choices. None of them is frozen.

type Pay

type Pay struct {
	Member *boxrec.Object
	Tx     *transaction.Transaction
	Sats   uint64
	// contains filtered or unexported fields
}

Pay is a payment built for an envelope and not yet sent: its fee coin is reserved. Envelope keeps it once the envelope is persisted (the coin is spent and the change held until the payment mines), and gives the coin back when the envelope is not.

func (*Pay) Abort

func (p *Pay) Abort()

Abort gives the payment's fee coin back to the pool.

type Receipt

type Receipt = state.Receipt

Receipt is one receipt sent, as Engine.Receipt returns it.

type Sent

type Sent = state.Sent

Sent is one envelope sent, as Engine.Envelope returns it.

type State

type State = state.State

State is a home's state: what was sent, acknowledged, swept and read, and what is still to be published.

func LoadState

func LoadState(dir, identity string) (*State, error)

LoadState reads a home's state for the identity (lowercase hex), or starts an empty one when the home has none. The caller holds the lock.

type Sweep

type Sweep = state.Sweep

Sweep is one retraction, as Engine.Retract returns it.

type SweepRefusedError

type SweepRefusedError struct {
	Txid string
	// Why is the refusal, as the evidence said it.
	Why string
	// Coin is what became of the fee coin.
	Coin string
}

SweepRefusedError is a sweep the network refused. The sweep is marked failed in the home, its fee coin is back in the pool when the node shows it unspent, and the outputs it named may be swept again.

func (*SweepRefusedError) Error

func (e *SweepRefusedError) Error() string

type Unicast

type Unicast = unicast.Set

Unicast is a set of hosts submitted to one by one (Legs.Hosts and Legs.Direct).

Jump to

Keyboard shortcuts

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