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 ¶
- Variables
- func CollectChange(ctx context.Context, pool *bwallet.Pool, proofs nodeapi.ProofSource) int
- func FeeError(err error) error
- func Journal(st *state.State, pool *bwallet.Pool, carried []bwallet.Output)
- func LockHome(dir string) (unlock func(), err error)
- func Minimal(mp *transaction.MerklePath, txid *chainhash.Hash) (*transaction.MerklePath, error)
- func Reconcile(ctx context.Context, st *state.State, pool *bwallet.Pool, ...) []bwallet.Output
- func Topic(office string) string
- func Unjournal(st *state.State)
- type Engine
- func (e *Engine) Close(ctx context.Context) error
- func (e *Engine) Envelope(ctx context.Context, l Letter) (sent *state.Sent, err error)
- func (e *Engine) FinishSweep(ctx context.Context) error
- func (e *Engine) NewPayer() *producer.Payer
- func (e *Engine) Payment(ctx context.Context, toHex string, sats uint64) (*Pay, error)
- func (e *Engine) Receipt(ctx context.Context, office string, txids []string) (*state.Receipt, error)
- func (e *Engine) RecoverTrees(ctx context.Context) error
- func (e *Engine) Resume(ctx context.Context) error
- func (e *Engine) Retract(ctx context.Context, txid string, vouts []uint32, offices []string) (*state.Sweep, error)
- func (e *Engine) Start(ctx context.Context) error
- func (e *Engine) TreeOf(txid string) (*funding.Tree, bool)
- func (e *Engine) Tx(txid string) (*transaction.Transaction, error)
- type Legs
- type Letter
- type Options
- type Pay
- type Receipt
- type Sent
- type State
- type Sweep
- type SweepRefusedError
- type Unicast
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrLocked = state.ErrLocked
ErrLocked is a home whose lock another process holds.
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.
var Params = boxrec.Params()
Params are the carrier parameters of every bbox carrier.
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.
var Retries = limits.Retries
Retries are the waits between the tries of a submission to the facade.
Functions ¶
func CollectChange ¶
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 Journal ¶
Journal makes every save of st record the pool, with the coins carried over from a reconciliation that could not settle them.
func LockHome ¶
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 ¶
func Minimal(mp *transaction.MerklePath, txid *chainhash.Hash) (*transaction.MerklePath, error)
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.
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 ¶
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 ¶
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)
}
Output:
func (*Engine) FinishSweep ¶
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) Payment ¶
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 ¶
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 ¶
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 ¶
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) 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.
type State ¶
State is a home's state: what was sent, acknowledged, swept and read, and what is still to be published.
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