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