testchain

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 6, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

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

Examples

Constants

View Source
const CoinbaseValue = 5_000_000_000

CoinbaseValue is what each mined block pays.

View Source
const Maturity = 100

Maturity is the depth at which coinbase may be spent.

Variables

View Source
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 New

func New(start uint32) *Chain

New is a chain whose tip is at height start.

func (*Chain) CurrentHeight

func (c *Chain) CurrentHeight(context.Context) (uint32, error)

CurrentHeight is the tip.

func (*Chain) Generate

func (c *Chain) Generate(n int, addr string) ([]string, error)

Generate mines n blocks whose coinbase pays addr, and returns their hashes. Waiting transactions are mined first.

func (*Chain) Height

func (c *Chain) Height() uint32

Height is the tip.

func (*Chain) Ingress

func (c *Chain) Ingress(l net.Listener)

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

func (c *Chain) IngressRefused() int64

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

func (c *Chain) Mine() int

Mine mines every accepted transaction still waiting, each in its own block, and returns how many.

func (*Chain) Mined

func (c *Chain) Mined(txid string) bool

Mined reports whether txid is in a block.

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) SetHold

func (c *Chain) SetHold(hold bool)

SetHold sets Hold while the chain serves.

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

func (c *Chain) SetRefuseKnown(refuse bool)

SetRefuseKnown sets RefuseKnown while the chain serves.

func (*Chain) SpendElsewhere

func (c *Chain) SpendElsewhere(txid string, vout uint32, by string)

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.

func (*Chain) Txids

func (c *Chain) Txids() []string

Txids are the txids of every transaction the chain holds, sorted.

func (*Chain) Waiting

func (c *Chain) Waiting() int

Waiting is how many accepted transactions are not yet mined.

Jump to

Keyboard shortcuts

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