chain.go raw

   1  // Package chain holds the per-chain data a compact-filter light client needs:
   2  // the P2P network magic, the genesis block, the expected genesis hash, and the
   3  // wallet-facing parameters that must not be mixed between chains.
   4  //
   5  // A profile is data, not a branch. The P2P and filter code is chain-agnostic;
   6  // only the profile differs.
   7  package chain
   8  
   9  import (
  10  	"errors"
  11  	"fmt"
  12  	"strings"
  13  
  14  	"git.smesh.lol/nostrino/pkg/btcwire"
  15  	"git.smesh.lol/nostrino/pkg/crypto/chainhash"
  16  )
  17  
  18  // Profile is one chain's parameters.
  19  type Profile struct {
  20  	// Name is the lowercase profile name used in URIs and flags.
  21  	Name string
  22  	// Magic is the P2P network magic.
  23  	Magic uint32
  24  	// GenesisHash is the expected hash of GenesisHeader.
  25  	GenesisHash chainhash.Hash
  26  	// GenesisHeader is the 80-byte genesis block header.
  27  	GenesisHeader btcwire.BlockHeader
  28  	// P2PPort is the conventional P2P port.
  29  	P2PPort string
  30  	// Bech32HRP is the segwit address prefix.
  31  	Bech32HRP string
  32  	// CoinType is the BIP44 coin type.
  33  	CoinType uint32
  34  	// PowLimitBits is the compact encoding of the maximum target.
  35  	PowLimitBits uint32
  36  	// TargetSpacing is the intended block interval in seconds.
  37  	TargetSpacing int64
  38  	// TargetTimespan is the difficulty adjustment window in seconds.
  39  	TargetTimespan int64
  40  	// RetargetInterval is the number of blocks in that window.
  41  	RetargetInterval uint32
  42  	// MinDifficulty enables the testnet rule that allows the pow limit after a
  43  	// long gap between blocks.
  44  	MinDifficulty bool
  45  	// NoRetarget keeps the target fixed (regtest).
  46  	NoRetarget bool
  47  	// Checkpoint optionally anchors a first sync below the tip. A checkpoint
  48  	// must pin both the block hash and the filter header at that height;
  49  	// note that the store implementation in this tree starts at genesis, so
  50  	// checkpoints are documentation for now.
  51  	Checkpoint *Checkpoint
  52  }
  53  
  54  // Checkpoint is a trusted anchor for a first sync.
  55  type Checkpoint struct {
  56  	Height       uint32
  57  	BlockHash    chainhash.Hash
  58  	FilterHeader chainhash.Hash
  59  }
  60  
  61  // Validate checks that the profile is complete and internally consistent.
  62  func (p *Profile) Validate() error {
  63  	if p == nil {
  64  		return errors.New("chain: nil profile")
  65  	}
  66  	if p.Name == "" {
  67  		return errors.New("chain: profile has no name")
  68  	}
  69  	if p.Magic == 0 {
  70  		return fmt.Errorf("chain: profile %q has no network magic", p.Name)
  71  	}
  72  	if p.GenesisHash == (chainhash.Hash{}) {
  73  		return fmt.Errorf("chain: profile %q has no genesis hash", p.Name)
  74  	}
  75  	got := p.GenesisHeader.BlockHash()
  76  	if got != p.GenesisHash {
  77  		return fmt.Errorf("chain: profile %q genesis header hashes to %s, want %s",
  78  			p.Name, got, p.GenesisHash)
  79  	}
  80  	if !p.GenesisHeader.CheckProofOfWork() {
  81  		return fmt.Errorf("chain: profile %q genesis header does not satisfy its target", p.Name)
  82  	}
  83  	if p.PowLimitBits == 0 {
  84  		return fmt.Errorf("chain: profile %q has no proof-of-work limit", p.Name)
  85  	}
  86  	if _, err := btcwire.CompactToBig(p.PowLimitBits); err != nil {
  87  		return fmt.Errorf("chain: profile %q pow limit: %w", p.Name, err)
  88  	}
  89  	if p.TargetSpacing <= 0 || p.TargetTimespan <= 0 || p.RetargetInterval == 0 {
  90  		return fmt.Errorf("chain: profile %q has an incomplete difficulty schedule", p.Name)
  91  	}
  92  	return nil
  93  }
  94  
  95  func mustHash(s string) chainhash.Hash {
  96  	h, err := chainhash.NewHashFromStr(s)
  97  	if err != nil {
  98  		panic("chain: bad hash constant: " + s)
  99  	}
 100  	return *h
 101  }
 102  
 103  func mustHeader(hexStr string) btcwire.BlockHeader {
 104  	b, err := hexDecode(hexStr)
 105  	if err != nil {
 106  		panic("chain: bad header constant: " + err.Error())
 107  	}
 108  	var h btcwire.BlockHeader
 109  	if err := h.Deserialize(b); err != nil {
 110  		panic("chain: bad header constant: " + err.Error())
 111  	}
 112  	return h
 113  }
 114  
 115  // The profiles implemented here. XBT is intentionally unconfigured: guessing
 116  // a fork's magic or genesis would silently point a client at the wrong chain,
 117  // so it must be supplied by the operator until its parameters are pinned.
 118  var (
 119  	// Bitcoin is the Bitcoin main network.
 120  	Bitcoin = Profile{
 121  		Name:        "btc",
 122  		Magic:       btcwire.MainNet,
 123  		GenesisHash: mustHash("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
 124  		GenesisHeader: mustHeader("0100000000000000000000000000000000000000000000000000000000000000" +
 125  			"000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" +
 126  			"29ab5f49ffff001d1dac2b7c"),
 127  		P2PPort:          "8333",
 128  		Bech32HRP:        "bc",
 129  		CoinType:         0,
 130  		PowLimitBits:     0x1d00ffff,
 131  		TargetSpacing:    600,
 132  		TargetTimespan:   1209600,
 133  		RetargetInterval: 2016,
 134  	}
 135  
 136  	// TestNet3 is the Bitcoin test network.
 137  	TestNet3 = Profile{
 138  		Name:        "testnet3",
 139  		Magic:       btcwire.TestNet3,
 140  		GenesisHash: mustHash("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"),
 141  		GenesisHeader: mustHeader("0100000000000000000000000000000000000000000000000000000000000000" +
 142  			"000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" +
 143  			"dae5494dffff001d1aa4ae18"),
 144  		P2PPort:          "18333",
 145  		Bech32HRP:        "tb",
 146  		CoinType:         1,
 147  		PowLimitBits:     0x1d00ffff,
 148  		TargetSpacing:    600,
 149  		TargetTimespan:   1209600,
 150  		RetargetInterval: 2016,
 151  		MinDifficulty:    true,
 152  	}
 153  
 154  	// Regtest is the Bitcoin regression test network.
 155  	Regtest = Profile{
 156  		Name:        "regtest",
 157  		Magic:       btcwire.RegTest,
 158  		GenesisHash: mustHash("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"),
 159  		GenesisHeader: mustHeader("0100000000000000000000000000000000000000000000000000000000000000" +
 160  			"000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" +
 161  			"dae5494dffff7f2002000000"),
 162  		P2PPort:          "18444",
 163  		Bech32HRP:        "bcrt",
 164  		CoinType:         1,
 165  		PowLimitBits:     0x207fffff,
 166  		TargetSpacing:    600,
 167  		TargetTimespan:   1209600,
 168  		RetargetInterval: 2016,
 169  		NoRetarget:       true,
 170  	}
 171  
 172  	// Signet is the default Bitcoin signet. Its genesis is the burn-signet
 173  	// genesis; a custom signet has different parameters.
 174  	Signet = Profile{
 175  		Name:        "signet",
 176  		Magic:       btcwire.SigNet,
 177  		GenesisHash: mustHash("00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"),
 178  		// The default signet genesis header is filled in by the operator when
 179  		// needed; the hash above is authoritative.
 180  		P2PPort:          "38333",
 181  		Bech32HRP:        "tb",
 182  		CoinType:         1,
 183  		PowLimitBits:     0x1e0377ae,
 184  		TargetSpacing:    600,
 185  		TargetTimespan:   1209600,
 186  		RetargetInterval: 2016,
 187  	}
 188  
 189  	// XBT is the second chain of the swap product. Its magic, genesis and
 190  	// filter index must be supplied by the operator until they are pinned.
 191  	XBT = Profile{Name: "xbt"}
 192  )
 193  
 194  // ByName returns a built-in profile.
 195  func ByName(name string) (*Profile, error) {
 196  	switch strings.ToLower(strings.TrimSpace(name)) {
 197  	case "btc", "bitcoin", "mainnet":
 198  		return &Bitcoin, nil
 199  	case "testnet3", "testnet", "test":
 200  		return &TestNet3, nil
 201  	case "regtest":
 202  		return &Regtest, nil
 203  	case "signet":
 204  		return &Signet, nil
 205  	case "xbt":
 206  		return &XBT, nil
 207  	default:
 208  		return nil, fmt.Errorf("chain: unknown profile %q", name)
 209  	}
 210  }
 211  
 212  // Names lists the built-in profile names.
 213  func Names() []string { return []string{"btc", "testnet3", "regtest", "signet", "xbt"} }
 214