// Package chain holds the per-chain data a compact-filter light client needs: // the P2P network magic, the genesis block, the expected genesis hash, and the // wallet-facing parameters that must not be mixed between chains. // // A profile is data, not a branch. The P2P and filter code is chain-agnostic; // only the profile differs. package chain import ( "errors" "fmt" "strings" "git.smesh.lol/nostrino/pkg/btcwire" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) // Profile is one chain's parameters. type Profile struct { // Name is the lowercase profile name used in URIs and flags. Name string // Magic is the P2P network magic. Magic uint32 // GenesisHash is the expected hash of GenesisHeader. GenesisHash chainhash.Hash // GenesisHeader is the 80-byte genesis block header. GenesisHeader btcwire.BlockHeader // P2PPort is the conventional P2P port. P2PPort string // Bech32HRP is the segwit address prefix. Bech32HRP string // CoinType is the BIP44 coin type. CoinType uint32 // PowLimitBits is the compact encoding of the maximum target. PowLimitBits uint32 // TargetSpacing is the intended block interval in seconds. TargetSpacing int64 // TargetTimespan is the difficulty adjustment window in seconds. TargetTimespan int64 // RetargetInterval is the number of blocks in that window. RetargetInterval uint32 // MinDifficulty enables the testnet rule that allows the pow limit after a // long gap between blocks. MinDifficulty bool // NoRetarget keeps the target fixed (regtest). NoRetarget bool // Checkpoint optionally anchors a first sync below the tip. A checkpoint // must pin both the block hash and the filter header at that height; // note that the store implementation in this tree starts at genesis, so // checkpoints are documentation for now. Checkpoint *Checkpoint } // Checkpoint is a trusted anchor for a first sync. type Checkpoint struct { Height uint32 BlockHash chainhash.Hash FilterHeader chainhash.Hash } // Validate checks that the profile is complete and internally consistent. func (p *Profile) Validate() error { if p == nil { return errors.New("chain: nil profile") } if p.Name == "" { return errors.New("chain: profile has no name") } if p.Magic == 0 { return fmt.Errorf("chain: profile %q has no network magic", p.Name) } if p.GenesisHash == (chainhash.Hash{}) { return fmt.Errorf("chain: profile %q has no genesis hash", p.Name) } got := p.GenesisHeader.BlockHash() if got != p.GenesisHash { return fmt.Errorf("chain: profile %q genesis header hashes to %s, want %s", p.Name, got, p.GenesisHash) } if !p.GenesisHeader.CheckProofOfWork() { return fmt.Errorf("chain: profile %q genesis header does not satisfy its target", p.Name) } if p.PowLimitBits == 0 { return fmt.Errorf("chain: profile %q has no proof-of-work limit", p.Name) } if _, err := btcwire.CompactToBig(p.PowLimitBits); err != nil { return fmt.Errorf("chain: profile %q pow limit: %w", p.Name, err) } if p.TargetSpacing <= 0 || p.TargetTimespan <= 0 || p.RetargetInterval == 0 { return fmt.Errorf("chain: profile %q has an incomplete difficulty schedule", p.Name) } return nil } func mustHash(s string) chainhash.Hash { h, err := chainhash.NewHashFromStr(s) if err != nil { panic("chain: bad hash constant: " + s) } return *h } func mustHeader(hexStr string) btcwire.BlockHeader { b, err := hexDecode(hexStr) if err != nil { panic("chain: bad header constant: " + err.Error()) } var h btcwire.BlockHeader if err := h.Deserialize(b); err != nil { panic("chain: bad header constant: " + err.Error()) } return h } // The profiles implemented here. XBT is intentionally unconfigured: guessing // a fork's magic or genesis would silently point a client at the wrong chain, // so it must be supplied by the operator until its parameters are pinned. var ( // Bitcoin is the Bitcoin main network. Bitcoin = Profile{ Name: "btc", Magic: btcwire.MainNet, GenesisHash: mustHash("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), GenesisHeader: mustHeader("0100000000000000000000000000000000000000000000000000000000000000" + "000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" + "29ab5f49ffff001d1dac2b7c"), P2PPort: "8333", Bech32HRP: "bc", CoinType: 0, PowLimitBits: 0x1d00ffff, TargetSpacing: 600, TargetTimespan: 1209600, RetargetInterval: 2016, } // TestNet3 is the Bitcoin test network. TestNet3 = Profile{ Name: "testnet3", Magic: btcwire.TestNet3, GenesisHash: mustHash("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"), GenesisHeader: mustHeader("0100000000000000000000000000000000000000000000000000000000000000" + "000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" + "dae5494dffff001d1aa4ae18"), P2PPort: "18333", Bech32HRP: "tb", CoinType: 1, PowLimitBits: 0x1d00ffff, TargetSpacing: 600, TargetTimespan: 1209600, RetargetInterval: 2016, MinDifficulty: true, } // Regtest is the Bitcoin regression test network. Regtest = Profile{ Name: "regtest", Magic: btcwire.RegTest, GenesisHash: mustHash("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"), GenesisHeader: mustHeader("0100000000000000000000000000000000000000000000000000000000000000" + "000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a" + "dae5494dffff7f2002000000"), P2PPort: "18444", Bech32HRP: "bcrt", CoinType: 1, PowLimitBits: 0x207fffff, TargetSpacing: 600, TargetTimespan: 1209600, RetargetInterval: 2016, NoRetarget: true, } // Signet is the default Bitcoin signet. Its genesis is the burn-signet // genesis; a custom signet has different parameters. Signet = Profile{ Name: "signet", Magic: btcwire.SigNet, GenesisHash: mustHash("00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6"), // The default signet genesis header is filled in by the operator when // needed; the hash above is authoritative. P2PPort: "38333", Bech32HRP: "tb", CoinType: 1, PowLimitBits: 0x1e0377ae, TargetSpacing: 600, TargetTimespan: 1209600, RetargetInterval: 2016, } // XBT is the second chain of the swap product. Its magic, genesis and // filter index must be supplied by the operator until they are pinned. XBT = Profile{Name: "xbt"} ) // ByName returns a built-in profile. func ByName(name string) (*Profile, error) { switch strings.ToLower(strings.TrimSpace(name)) { case "btc", "bitcoin", "mainnet": return &Bitcoin, nil case "testnet3", "testnet", "test": return &TestNet3, nil case "regtest": return &Regtest, nil case "signet": return &Signet, nil case "xbt": return &XBT, nil default: return nil, fmt.Errorf("chain: unknown profile %q", name) } } // Names lists the built-in profile names. func Names() []string { return []string{"btc", "testnet3", "regtest", "signet", "xbt"} }