Documentation
¶
Overview ¶
Package testchain is a local stand-in for the chain an application's tests and local trials run against: a chain that mines what it is sent, served as a node's JSON-RPC (generatetoaddress, sendrawtransaction, getinfo) and asset API, as an ARC-compatible broadcaster under /arcade, as a fabric ingress (Ingress), and as a header source in the overlay bridge's native shape (/v1/root/<height>, /v1/tip). It is also a chain tracker. It checks what it is sent as a node would in the ways that matter here: inputs exist and are unspent, coinbase is mature, scripts verify, and nothing non-final is mined.
Each transaction is mined in a block of its own, beside a fixed sibling, so every proof is one level deep and every header is a root the chain computed. There is no proof of work and no real block.
It is a test helper that lives outside a _test file so that several modules can share it. Nothing here is for real use, and nothing that moves value should import it.
Index ¶
- Constants
- Variables
- type Chain
- func (c *Chain) CurrentHeight(context.Context) (uint32, error)
- func (c *Chain) Generate(n int, addr string) ([]string, error)
- func (c *Chain) Height() uint32
- func (c *Chain) Ingress(l net.Listener)
- func (c *Chain) IngressRefused() int64
- func (c *Chain) IsValidRootForHeight(_ context.Context, root *chainhash.Hash, height uint32) (bool, error)
- func (c *Chain) Mine() int
- func (c *Chain) Mined(txid string) bool
- func (c *Chain) Proof(txid string) (*transaction.MerklePath, uint32, bool)
- func (c *Chain) Send(tx *transaction.Transaction) error
- func (c *Chain) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (c *Chain) SetHold(hold bool)
- func (c *Chain) SetHoldIf(f func(tx *transaction.Transaction) bool)
- func (c *Chain) SetRefuseKnown(refuse bool)
- func (c *Chain) SpendElsewhere(txid string, vout uint32, by string)
- func (c *Chain) Tx(txid string) *transaction.Transaction
- func (c *Chain) Txids() []string
- func (c *Chain) Waiting() int
Examples ¶
Constants ¶
const CoinbaseValue = 5_000_000_000
CoinbaseValue is what each mined block pays.
const Maturity = 100
Maturity is the depth at which coinbase may be spent.
Variables ¶
var ErrAlreadyKnown = errors.New("txn-already-known")
ErrAlreadyKnown is Send refusing, with RefuseKnown set, a transaction the chain already holds. Its text is one a node's sendrawtransaction answers for a transaction it already has.
Functions ¶
This section is empty.
Types ¶
type Chain ¶
type Chain struct {
// Hold keeps accepted transactions unmined until Mine; otherwise each
// is mined as it is accepted. HoldIf holds only those it answers true
// for.
Hold bool
HoldIf func(tx *transaction.Transaction) bool
// Refuse, when set, is asked about each transaction sent; a non-empty
// answer refuses it with that reason.
Refuse func(tx *transaction.Transaction) string
// Busy, when set, is asked about each transaction sent through the RPC
// or arcade; true answers 503, which refuses nothing: a transient
// failure the sender tries again.
Busy func(tx *transaction.Transaction) bool
// RefuseKnown makes Send refuse a transaction the chain already holds,
// with ErrAlreadyKnown, as a node's sendrawtransaction may refuse one it
// has in its mempool or in a block. Without it Send takes such a
// transaction again and does nothing. The broadcaster under /arcade is
// not
// changed by it: it answers the status of a transaction it holds, as an
// ARC-compatible broadcaster does.
RefuseKnown bool
// Sent counts the transactions accepted.
Sent int
// contains filtered or unexported fields
}
Chain is the local chain.
Example ¶
A chain in the process: coinbase mined to the fixed test key, a spend of the first one sent and mined in a block of its own, and its proof verified against the chain as a tracker. Served over HTTP (it is an http.Handler), the same chain is a node, a broadcaster and a header source for the clients.
package main
import (
"context"
"fmt"
"github.com/bsv-blockchain/go-sdk/script"
"github.com/bsv-blockchain/go-sdk/transaction"
"github.com/bsv-blockchain/go-sdk/transaction/template/p2pkh"
"github.com/lightwebinc/bcommon/goldentest"
"github.com/lightwebinc/bcommon/testchain"
)
func main() {
ctx := context.Background()
key := goldentest.FixedKey()
addr, err := script.NewAddressFromPublicKey(key.PubKey(), false)
if err != nil {
fmt.Println(err)
return
}
c := testchain.New(700)
if _, err := c.Generate(testchain.Maturity+1, addr.AddressString); err != nil {
fmt.Println(err)
return
}
fmt.Println("tip:", c.Height())
// The oldest coinbase is mature; the newest is not.
var oldest, newest *transaction.Transaction
for _, id := range c.Txids() {
tx := c.Tx(id)
_, h, _ := c.Proof(id)
if h == 701 {
oldest = tx
}
if h == c.Height() {
newest = tx
}
}
spend := func(coin *transaction.Transaction, fee uint64) *transaction.Transaction {
unlock, _ := p2pkh.Unlock(key, nil)
tx := transaction.NewTransaction()
tx.AddInputFromTx(coin, 0, unlock)
tx.AddOutput(&transaction.TransactionOutput{Satoshis: coin.Outputs[0].Satoshis - fee, LockingScript: coin.Outputs[0].LockingScript})
if err := tx.Sign(); err != nil {
panic(err)
}
return tx
}
fmt.Println("young coinbase:", c.Send(spend(newest, 500)))
tx := spend(oldest, 500)
fmt.Println("mature coinbase:", c.Send(tx))
mp, height, mined := c.Proof(tx.TxID().String())
ok, err := mp.Verify(ctx, tx.TxID(), c)
fmt.Println("mined:", mined, "at", height, "proof verifies:", ok, err)
fmt.Println("the same output spent by another transaction:", c.Send(spend(oldest, 600)) != nil)
}
Output: tip: 801 young coinbase: input 0 spends immature coinbase mature coinbase: <nil> mined: true at 802 proof verifies: true <nil> the same output spent by another transaction: true
func (*Chain) CurrentHeight ¶
CurrentHeight is the tip.
func (*Chain) Generate ¶
Generate mines n blocks whose coinbase pays addr, and returns their hashes. Waiting transactions are mined first.
func (*Chain) Ingress ¶
Ingress is a fabric ingress as publish.TCPIngress writes to: one transaction per connection, in Extended Format only, handed to Send. A raw transaction, or one whose inputs do not carry the outputs they spend, is refused (its connection is closed and it is counted), as a settlement leg that takes EF refuses it. It serves until l is closed.
func (*Chain) IngressRefused ¶
IngressRefused is how many submissions the ingress refused.
func (*Chain) IsValidRootForHeight ¶
func (c *Chain) IsValidRootForHeight(_ context.Context, root *chainhash.Hash, height uint32) (bool, error)
IsValidRootForHeight is the chain as a chain tracker.
func (*Chain) Mine ¶
Mine mines every accepted transaction still waiting, each in its own block, and returns how many.
func (*Chain) Proof ¶
func (c *Chain) Proof(txid string) (*transaction.MerklePath, uint32, bool)
Proof is txid's proof and height, if it mined.
func (*Chain) Send ¶
func (c *Chain) Send(tx *transaction.Transaction) error
Send takes a transaction as a node's sendrawtransaction does. A transaction the chain already holds is taken again with nothing done and no error, or, with RefuseKnown set, refused with ErrAlreadyKnown. That holds for a transaction SpendElsewhere displaced too: the chain keeps and serves it, as a node may, so it is one the chain already holds.
func (*Chain) ServeHTTP ¶
func (c *Chain) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves the node's JSON-RPC at /rpc, its asset API under /api/v1/, the header source at /v1/root/<height> and /v1/tip, and the broadcaster under /arcade/.
func (*Chain) SetHoldIf ¶
func (c *Chain) SetHoldIf(f func(tx *transaction.Transaction) bool)
SetHoldIf sets HoldIf while the chain serves.
func (*Chain) SetRefuseKnown ¶ added in v0.6.3
SetRefuseKnown sets RefuseKnown while the chain serves.
func (*Chain) SpendElsewhere ¶
SpendElsewhere marks output vout of txid spent by the transaction by, as a competing spend the chain took from someone else would: the output is gone and the UTXO view names by as its spender.
func (*Chain) Tx ¶
func (c *Chain) Tx(txid string) *transaction.Transaction
Tx is a transaction the chain knows.