Documentation
¶
Overview ¶
Package nodeapi talks to a Teranode node over HTTP: JSON-RPC for mining and direct submission, the asset API for reading what got mined.
It is the funding and settlement side of a producer, and nothing a reader depends on: a reader verifies against a header source, never against a node it would have to trust.
Every response body is bounded before it is parsed and every declared count in a binary proof is checked against the bytes present before the SDK is allowed to allocate for it. The node is trusted to be honest about the chain, which is not the same as trusting it to be well behaved about response size (the same rule the headers package states).
The JSON shapes decoded here are the ones a Teranode node's RPC and asset API answer. The tests' response bodies are written to those shapes, not captured from a live node.
Index ¶
- Variables
- func IsHTTP(err error, status int) bool
- func ProofFor(raw []byte, txid string) (*transaction.MerklePath, error)
- func WaitMined(ctx context.Context, asset *Asset, txid string, poll time.Duration) (*transaction.MerklePath, uint32, error)
- func WaitSettled(ctx context.Context, asset *Asset, tx *transaction.Transaction, ...) (*transaction.MerklePath, uint32, error)
- type Asset
- func (a *Asset) BestHeader(ctx context.Context) (*Header, error)
- func (a *Asset) Block(ctx context.Context, hash string) (*Block, error)
- func (a *Asset) HashAtHeight(ctx context.Context, height uint32) (string, error)
- func (a *Asset) Header(ctx context.Context, hash string) (*Header, error)
- func (a *Asset) MerkleProof(ctx context.Context, txid string) (*transaction.MerklePath, error)
- func (a *Asset) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
- func (a *Asset) Spender(ctx context.Context, txid string, vout uint32) (string, error)
- func (a *Asset) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
- func (a *Asset) TxMeta(ctx context.Context, txid string) (*TxMeta, error)
- func (a *Asset) TxRaw(ctx context.Context, txid string) ([]byte, error)
- type Block
- type HTTPError
- type Header
- type Info
- type RPC
- func (r *RPC) Call(ctx context.Context, method string, params []any, out any) error
- func (r *RPC) GenerateToAddress(ctx context.Context, n int, addr string) ([]string, error)
- func (r *RPC) GetInfo(ctx context.Context) (*Info, error)
- func (r *RPC) SendRawTransaction(ctx context.Context, rawHex string) (string, error)
- type SpentError
- type TxMeta
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrBodyTooLarge = errors.New("nodeapi: response body exceeds the bound")
ErrBodyTooLarge refuses a response above the bound.
var ErrDoubleSpent = errors.New("an input is spent by another transaction")
ErrDoubleSpent is a transaction one of whose inputs the node shows spent by another transaction. It never mines. Every error this package and the packages built on it return for that case wraps it, so errors.Is finds it whatever the broadcaster said.
var ErrNoID = errors.New("nodeapi: rpc: no request id")
ErrNoID refuses a call on an RPC with no request id. The library has no id of its own to fall back on, and any default would put one application's name on another's requests.
var ErrNotMined = errors.New("nodeapi: not mined")
ErrNotMined reports a transaction the node has not placed in a main-chain block yet, or a proof it cannot build yet. It is a state, not a failure: callers poll again after the next block.
Functions ¶
func ProofFor ¶
func ProofFor(raw []byte, txid string) (*transaction.MerklePath, error)
ProofFor parses a BUMP someone else supplied as the proof for txid, and refuses one that does not name txid at its leaf level. Every source of a proof goes through here: the bytes come from a service, and a path for some other transaction would build a BEEF that no host accepts. The proof is held to the same bound as every answer this package reads.
Example ¶
ProofFor is where every proof a service supplies enters: the guard walks it before the SDK parses it, and a proof that does not name the transaction it was asked for is refused, because it would verify perfectly and prove nothing.
package main
import (
"crypto/sha256"
"fmt"
"github.com/bsv-blockchain/go-sdk/chainhash"
"github.com/bsv-blockchain/go-sdk/transaction"
"github.com/lightwebinc/bcommon/nodeapi"
)
func main() {
txid := chainhash.Hash(sha256.Sum256([]byte("a transaction")))
sibling := chainhash.Hash(sha256.Sum256([]byte("its neighbour")))
isTxid := true
raw := transaction.NewMerklePath(90, [][]*transaction.PathElement{{
{Offset: 0, Hash: &sibling},
{Offset: 1, Hash: &txid, Txid: &isTxid},
}}).Bytes()
mp, err := nodeapi.ProofFor(raw, txid.String())
fmt.Println("asked for:", mp != nil, err)
unrelated := chainhash.Hash(sha256.Sum256([]byte("some other transaction")))
_, err = nodeapi.ProofFor(raw, unrelated.String())
fmt.Println("asked for another:", err)
_, err = nodeapi.ProofFor(raw[:len(raw)-1], txid.String())
fmt.Println("truncated:", err)
}
Output: asked for: true <nil> asked for another: proof does not contain the txid truncated: bump ends mid-structure
func WaitMined ¶
func WaitMined(ctx context.Context, asset *Asset, txid string, poll time.Duration) (*transaction.MerklePath, uint32, error)
WaitMined polls until txid is placed in a main-chain block and its proof is served, or ctx ends. It returns the proof and the block height.
func WaitSettled ¶ added in v0.5.4
func WaitSettled(ctx context.Context, asset *Asset, tx *transaction.Transaction, poll time.Duration) (*transaction.MerklePath, uint32, error)
WaitSettled is WaitMined for a transaction the caller holds: while tx has not mined, each poll also asks the node whether one of its inputs is spent by another transaction, and returns that *SpentError (errors.Is ErrDoubleSpent) at once rather than waiting until ctx ends.
Types ¶
type Asset ¶
type Asset struct {
// Base is the API root with no path suffix, e.g. http://node.example.com:20090.
Base string
// Client is optional; the default has a 30s timeout.
Client *http.Client
}
Asset is a Teranode asset-HTTP-API client.
func (*Asset) BestHeader ¶
BestHeader reads the tip.
func (*Asset) HashAtHeight ¶
HashAtHeight resolves a main-chain block hash by height through the paged block list, where offset counts back from the tip. The tip can move between the two reads, so the answer is checked against the height asked for and the read is retried when it does.
func (*Asset) MerkleProof ¶
func (a *Asset) MerkleProof(ctx context.Context, txid string) (*transaction.MerklePath, error)
MerkleProof returns the BRC-74 BUMP for a mined transaction, parsed.
The node answers 404 for a transaction it has not placed in a main-chain block and 500 for a proof with an empty path (a single-transaction block); both are ErrNotMined to a caller that will ask again after the next block.
The bytes are walked by the guard package before the SDK parses them, and the parse runs under a recover, so a malformed proof is an error and never a crash. The parsed path must also contain txid at its leaf level: a proof the node served for the wrong transaction verifies perfectly and proves nothing.
func (*Asset) Proof ¶
func (a *Asset) Proof(ctx context.Context, txid string) (*transaction.MerklePath, uint32, error)
Proof asks once whether txid is mined and returns its proof and height if so, or ErrNotMined. It is WaitMined without the waiting, for a caller that collects proofs later rather than blocking on them.
func (*Asset) Spender ¶ added in v0.5.4
Spender reads the node's UTXO view of txid (/api/v1/utxos/{txid}/json) and names the transaction that spent output vout, or "" while it is unspent. A transaction the node does not know is an *HTTPError with status 404.
func (*Asset) SpentElsewhere ¶ added in v0.5.4
func (a *Asset) SpentElsewhere(ctx context.Context, tx *transaction.Transaction) error
SpentElsewhere returns a *SpentError for the first input of tx the node shows spent by another transaction, or nil when there is none: then tx can still mine as far as its inputs go. An input the node cannot answer for (a parent it does not know, a read that failed) is passed over, so this refuses only on the node's positive word.
func (*Asset) TxMeta ¶
TxMeta reads a transaction's placement. A 404 is ErrNotMined: for a submission in flight, "never seen" and "not placed yet" are one state.
func (*Asset) TxRaw ¶
TxRaw returns a transaction's raw serialisation. A node may answer in the raw form or in Extended Format (BRC-30), the raw form with each input's previous output added, as a Teranode asset API does; either is walked by guard.RawTransaction and the raw form returned, so a caller always gets bytes guard.ParseTransaction reads. The previous outputs an Extended Format answer carries are dropped.
type Block ¶
type Block struct {
Hash string `json:"hash"`
Height uint32 `json:"height"`
CoinbaseTx struct {
TxID string `json:"txid"`
Outputs []struct {
Satoshis uint64 `json:"satoshis"`
LockingScript string `json:"lockingScript"`
} `json:"outputs"`
} `json:"coinbase_tx"`
}
Block is the subset of /api/v1/block/{hash}/json read here: the coinbase outputs are what fund the pool.
type HTTPError ¶
HTTPError is a non-2xx answer, kept so callers can distinguish a 404 from a transport failure.
type Header ¶
type Header struct {
Hash string `json:"hash"`
Prev string `json:"previousblockhash"`
MerkleRoot string `json:"merkleroot"`
Height uint32 `json:"height"`
}
Header is the subset of a block header answer a caller needs.
type Info ¶
type Info struct {
Blocks int64 `json:"blocks"`
Connections int64 `json:"connections"`
Version int64 `json:"version"`
}
Info is the subset of getinfo used here. Teranode registers getblockcount but answers "Command unimplemented", so the height comes from here.
type RPC ¶
type RPC struct {
// URL is the RPC endpoint, e.g. http://node.example.com:21292/.
URL string
User string
Pass string
// ID is the JSON-RPC request id, sent verbatim on every call. It is the
// application's to choose and it is required: Call refuses an empty one
// before any request is sent rather than choosing one itself.
ID string
// Client is optional; the default has a 60s timeout, which is long
// because generatetoaddress mines inline. The lever for a slow node is
// the batch size, not this.
Client *http.Client
}
RPC is a Teranode JSON-RPC client.
func (*RPC) Call ¶
Call issues one JSON-RPC call and unmarshals the result into out, which may be nil. An RPC-level error is returned as the node phrased it. An RPC with no ID is refused with ErrNoID before anything is sent.
func (*RPC) GenerateToAddress ¶
GenerateToAddress mines n blocks paying the coinbase to addr and returns the block hashes. Keep n small: a large call can outlast the RPC timeout, which is why FundFromCoinbase mines in batches.
type SpentError ¶ added in v0.5.4
type SpentError struct {
Txid string
// Input is the index of the input in Txid; Outpoint is what it spends,
// txid.vout.
Input int
Outpoint string
By string
}
SpentError names the input of Txid that the node shows spent by By.
func (*SpentError) Error ¶ added in v0.5.4
func (e *SpentError) Error() string
func (*SpentError) Unwrap ¶ added in v0.5.4
func (e *SpentError) Unwrap() error
Unwrap makes a *SpentError errors.Is ErrDoubleSpent.
type TxMeta ¶
type TxMeta struct {
BlockHashes []string `json:"blockHashes"`
BlockHeights []uint32 `json:"blockHeights"`
SubtreeIdxs []int `json:"subtreeIdxs"`
MainChainIndex int `json:"mainChainIndex"`
IsCoinbase bool `json:"isCoinbase"`
}
TxMeta is the placement subset of /api/v1/txmeta/{txid}/json. The slices are parallel, one entry per block the transaction landed in, and MainChainIndex selects the main-chain one.