mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-04-29 12:15:13 +08:00
Compare commits
14 Commits
v0.4.0
...
fix/runes-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2225fa64ae | ||
|
|
1bd1a362ba | ||
|
|
3c5907c94d | ||
|
|
b1d9f4f574 | ||
|
|
6a5ba528a8 | ||
|
|
6484887710 | ||
|
|
9a1382fb9f | ||
|
|
3d5f3b414c | ||
|
|
6e8a846c27 | ||
|
|
8b690c4f7f | ||
|
|
cc37807ff9 | ||
|
|
9ab16d21e1 | ||
|
|
32fec89914 | ||
|
|
0131de6717 |
@@ -1,5 +1,7 @@
|
||||
<!-- omit from toc -->
|
||||
|
||||
- [Türkçe](https://github.com/Rumeyst/gaze-indexer/blob/turkish-translation/docs/README_tr.md)
|
||||
|
||||
# Gaze Indexer
|
||||
|
||||
Gaze Indexer is an open-source and modular indexing client for Bitcoin meta-protocols with **Unified Consistent APIs** across fungible token protocols.
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/constants"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale"
|
||||
"github.com/gaze-network/indexer-network/modules/runes"
|
||||
runesconstants "github.com/gaze-network/indexer-network/modules/runes/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versions = map[string]string{
|
||||
"": constants.Version,
|
||||
"runes": runes.Version,
|
||||
"runes": runesconstants.Version,
|
||||
"nodesale": nodesale.Version,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
package common
|
||||
|
||||
// HalvingInterval is the number of blocks between each halving event.
|
||||
const HalvingInterval = 210_000
|
||||
@@ -1,22 +1,31 @@
|
||||
package common
|
||||
|
||||
import "github.com/btcsuite/btcd/chaincfg"
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
)
|
||||
|
||||
type Network string
|
||||
|
||||
const (
|
||||
NetworkMainnet Network = "mainnet"
|
||||
NetworkTestnet Network = "testnet"
|
||||
NetworkMainnet Network = "mainnet"
|
||||
NetworkTestnet Network = "testnet"
|
||||
NetworkFractalMainnet Network = "fractal-mainnet"
|
||||
NetworkFractalTestnet Network = "fractal-testnet"
|
||||
)
|
||||
|
||||
var supportedNetworks = map[Network]struct{}{
|
||||
NetworkMainnet: {},
|
||||
NetworkTestnet: {},
|
||||
NetworkMainnet: {},
|
||||
NetworkTestnet: {},
|
||||
NetworkFractalMainnet: {},
|
||||
NetworkFractalTestnet: {},
|
||||
}
|
||||
|
||||
var chainParams = map[Network]*chaincfg.Params{
|
||||
NetworkMainnet: &chaincfg.MainNetParams,
|
||||
NetworkTestnet: &chaincfg.TestNet3Params,
|
||||
NetworkMainnet: &chaincfg.MainNetParams,
|
||||
NetworkTestnet: &chaincfg.TestNet3Params,
|
||||
NetworkFractalMainnet: &chaincfg.MainNetParams,
|
||||
NetworkFractalTestnet: &chaincfg.MainNetParams,
|
||||
}
|
||||
|
||||
func (n Network) IsSupported() bool {
|
||||
@@ -31,3 +40,15 @@ func (n Network) ChainParams() *chaincfg.Params {
|
||||
func (n Network) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
func (n Network) HalvingInterval() uint64 {
|
||||
switch n {
|
||||
case NetworkMainnet, NetworkTestnet:
|
||||
return 210_000
|
||||
case NetworkFractalMainnet, NetworkFractalTestnet:
|
||||
return 2_100_000
|
||||
default:
|
||||
logger.Panic("invalid network")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
165
docs/README_tr.md
Normal file
165
docs/README_tr.md
Normal file
@@ -0,0 +1,165 @@
|
||||
## Çeviriler
|
||||
- [English (İngilizce)](../README.md)
|
||||
|
||||
**Son Güncelleme:** 21 Ağustos 2024
|
||||
> **Not:** Bu belge, topluluk tarafından yapılmış bir çeviridir. Ana README.md dosyasındaki güncellemeler buraya otomatik olarak yansıtılmayabilir. En güncel bilgiler için [İngilizce sürümü](../README.md) inceleyin.
|
||||
|
||||
|
||||
# Gaze Indexer
|
||||
|
||||
Gaze Indexer, değiştirilebilir token protokolleri arasında **Birleştirilmiş Tutarlı API'lere** sahip Bitcoin meta-protokolleri için açık kaynaklı ve modüler bir indeksleme istemcisidir.
|
||||
|
||||
Gaze Indexer, kullanıcıların tüm modülleri tek bir komutla tek bir monolitik örnekte veya dağıtılmış bir mikro hizmet kümesi olarak çalıştırmasına olanak tanıyan **modülerlik** göz önünde bulundurularak oluşturulmuştur.
|
||||
|
||||
Gaze Indexer, verimli veri getirme, yeniden düzenleme algılama ve veritabanı taşıma aracı ile HERHANGİ bir meta-protokol indeksleyici oluşturmak için bir temel görevi görür.
|
||||
Bu, geliştiricilerin **gerçekten** önemli olana odaklanmasını sağlar: Meta-protokol indeksleme mantığı. Yeni meta-protokoller, yeni modüller uygulanarak kolayca eklenebilir.
|
||||
|
||||
- [Modüller](#modules)
|
||||
- [1. Runes](#1-runes)
|
||||
- [Kurulum](#installation)
|
||||
- [Önkoşullar](#prerequisites)
|
||||
- [1. Donanım Gereksinimleri](#1-hardware-requirements)
|
||||
- [2. Bitcoin Core RPC sunucusunu hazırlayın.](#2-prepare-bitcoin-core-rpc-server)
|
||||
- [3. Veritabanı hazırlayın.](#3-prepare-database)
|
||||
- [4. `config.yaml` dosyasını hazırlayın.](#4-prepare-configyaml-file)
|
||||
- [Docker ile yükle (önerilir)](#install-with-docker-recommended)
|
||||
- [Kaynaktan yükle](#install-from-source)
|
||||
|
||||
## Modüller
|
||||
|
||||
### 1. Runes
|
||||
|
||||
Runes Dizinleyici ilk meta-protokol dizinleyicimizdir. Bitcoin işlemlerini kullanarak Runes durumlarını, işlemlerini, rün taşlarını ve bakiyelerini indeksler.
|
||||
Geçmiş Runes verilerini sorgulamak için bir dizi API ile birlikte gelir. Tüm ayrıntılar için [API Referansı] (https://api-docs.gaze.network) adresimize bakın.
|
||||
|
||||
|
||||
## Kurulum
|
||||
|
||||
### Önkoşullar
|
||||
|
||||
#### 1. Donanım Gereksinimleri
|
||||
|
||||
Her modül farklı donanım gereksinimleri gerektirir.
|
||||
| Modül | CPU | RAM |
|
||||
| ------ | --------- | ---- |
|
||||
| Runes | 0,5 çekirdek | 1 GB |
|
||||
|
||||
#### 2. Bitcoin Core RPC sunucusunu hazırlayın.
|
||||
|
||||
Gaze Indexer'ın işlem verilerini kendi barındırdığı ya da QuickNode gibi yönetilen sağlayıcıları kullanan bir Bitcoin Core RPC'den alması gerekir.
|
||||
Bir Bitcoin Core'u kendiniz barındırmak için bkz. https://bitcoin.org/en/full-node.
|
||||
|
||||
#### 3. Veritabanını hazırlayın.
|
||||
|
||||
Gaze Indexer PostgreSQL için birinci sınıf desteğe sahiptir. Diğer veritabanlarını kullanmak isterseniz, her modülün Veri Ağ Geçidi arayüzünü karşılayan kendi veritabanı havuzunuzu uygulayabilirsiniz.
|
||||
İşte her modül için minimum veritabanı disk alanı gereksinimimiz.
|
||||
| Modül | Veritabanı Depolama Alanı (mevcut) | Veritabanı Depolama Alanı (1 yıl içinde) |
|
||||
| ------ | -------------------------- | ---------------------------- |
|
||||
| Runes | 10 GB | 150 GB |
|
||||
|
||||
#### 4. config.yaml` dosyasını hazırlayın.
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
logger:
|
||||
output: TEXT # Output format for logs. current supported formats: "TEXT" | "JSON" | "GCP"
|
||||
debug: false
|
||||
|
||||
# Network to run the indexer on. Current supported networks: "mainnet" | "testnet"
|
||||
network: mainnet
|
||||
|
||||
# Bitcoin Core RPC configuration options.
|
||||
bitcoin_node:
|
||||
host: "" # [Required] Host of Bitcoin Core RPC (without https://)
|
||||
user: "" # Username to authenticate with Bitcoin Core RPC
|
||||
pass: "" # Password to authenticate with Bitcoin Core RPC
|
||||
disable_tls: false # Set to true to disable tls
|
||||
|
||||
# Block reporting configuration options. See Block Reporting section for more details.
|
||||
reporting:
|
||||
disabled: false # Set to true to disable block reporting to Gaze Network. Default is false.
|
||||
base_url: "https://indexer.api.gaze.network" # Defaults to "https://indexer.api.gaze.network" if left empty
|
||||
name: "" # [Required if not disabled] Name of this indexer to show on the Gaze Network dashboard
|
||||
website_url: "" # Public website URL to show on the dashboard. Can be left empty.
|
||||
indexer_api_url: "" # Public url to access this indexer's API. Can be left empty if you want to keep your indexer private.
|
||||
|
||||
# HTTP server configuration options.
|
||||
http_server:
|
||||
port: 8080 # Port to run the HTTP server on for modules with HTTP API handlers.
|
||||
|
||||
# Meta-protocol modules configuration options.
|
||||
modules:
|
||||
# Configuration options for Runes module. Can be removed if not used.
|
||||
runes:
|
||||
database: "postgres" # Database to store Runes data. current supported databases: "postgres"
|
||||
datasource: "bitcoin-node" # Data source to be used for Bitcoin data. current supported data sources: "bitcoin-node".
|
||||
api_handlers: # API handlers to enable. current supported handlers: "http"
|
||||
- http
|
||||
postgres:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "password"
|
||||
db_name: "postgres"
|
||||
# url: "postgres://postgres:password@localhost:5432/postgres?sslmode=prefer" # [Optional] This will override other database credentials above.
|
||||
```
|
||||
|
||||
### Docker ile yükleyin (önerilir)
|
||||
|
||||
Kurulum kılavuzumuz için `docker-compose` kullanacağız. Docker-compose.yaml` dosyasının `config.yaml` dosyası ile aynı dizinde olduğundan emin olun.
|
||||
|
||||
```yaml
|
||||
# docker-compose.yaml
|
||||
services:
|
||||
gaze-indexer:
|
||||
image: ghcr.io/gaze-network/gaze-indexer:v0.2.1
|
||||
container_name: gaze-indexer
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080 # Expose HTTP server port to host
|
||||
volumes:
|
||||
- "./config.yaml:/app/config.yaml" # mount config.yaml file to the container as "/app/config.yaml"
|
||||
command: ["/app/main", "run", "--modules", "runes"] # Put module flags after "run" commands to select which modules to run.
|
||||
```
|
||||
|
||||
### Kaynaktan yükleyin
|
||||
|
||||
1. Go` sürüm 1.22 veya daha üstünü yükleyin. Go kurulum kılavuzuna bakın [burada](https://go.dev/doc/install).
|
||||
2. Bu depoyu klonlayın.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/gaze-network/gaze-indexer.git
|
||||
cd gaze-indexer
|
||||
```
|
||||
|
||||
3. Ana ikili dosyayı oluşturun.
|
||||
|
||||
```bash
|
||||
# Bağımlılıkları al
|
||||
go mod indir
|
||||
|
||||
# Ana ikili dosyayı oluşturun
|
||||
go build -o gaze main.go
|
||||
```
|
||||
|
||||
4. Veritabanı geçişlerini `migrate` komutu ve modül bayrakları ile çalıştırın.
|
||||
|
||||
```bash
|
||||
./gaze migrate up --runes --database postgres://postgres:password@localhost:5432/postgres
|
||||
```
|
||||
|
||||
5. Dizinleyiciyi `run` komutu ve modül bayrakları ile başlatın.
|
||||
|
||||
```bash
|
||||
./gaze run --modules runes
|
||||
```
|
||||
|
||||
Eğer `config.yaml` dosyası `./app/config.yaml` adresinde bulunmuyorsa, `config.yaml` dosyasının yolunu belirtmek için `--config` bayrağını kullanın.
|
||||
|
||||
```bash
|
||||
./gaze run --modules runes --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
|
||||
## Çeviriler
|
||||
- [English (İngilizce)](../README.md)
|
||||
@@ -1,28 +1,12 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/constants"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var startingBlockHeader = map[common.Network]types.BlockHeader{
|
||||
common.NetworkMainnet: {
|
||||
Height: 839999,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("0000000000000000000172014ba58d66455762add0512355ad651207918494ab")),
|
||||
PrevBlock: *utils.Must(chainhash.NewHashFromStr("00000000000000000001dcce6ce7c8a45872cafd1fb04732b447a14a91832591")),
|
||||
},
|
||||
common.NetworkTestnet: {
|
||||
Height: 2583200,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("000000000006c5f0dfcd9e0e81f27f97a87aef82087ffe69cd3c390325bb6541")),
|
||||
PrevBlock: *utils.Must(chainhash.NewHashFromStr("00000000000668f3bafac992f53424774515440cb47e1cb9e73af3f496139e28")),
|
||||
},
|
||||
}
|
||||
|
||||
type getCurrentBlockResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Height int64 `json:"height"`
|
||||
@@ -36,7 +20,7 @@ func (h *HttpHandler) GetCurrentBlock(ctx *fiber.Ctx) (err error) {
|
||||
if !errors.Is(err, errs.NotFound) {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeader = startingBlockHeader[h.network]
|
||||
blockHeader = constants.StartingBlockHeader[h.network]
|
||||
}
|
||||
|
||||
resp := getCurrentBlockResponse{
|
||||
|
||||
@@ -51,6 +51,7 @@ type getHoldersResult struct {
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
TotalSupply uint128.Uint128 `json:"totalSupply"`
|
||||
MintedAmount uint128.Uint128 `json:"mintedAmount"`
|
||||
Decimals uint8 `json:"decimals"`
|
||||
List []holdingBalance `json:"list"`
|
||||
}
|
||||
|
||||
@@ -140,6 +141,7 @@ func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
||||
BlockHeight: blockHeight,
|
||||
TotalSupply: totalSupply,
|
||||
MintedAmount: mintedAmount,
|
||||
Decimals: runeEntry.Divisibility,
|
||||
List: list,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ func resolvePkScript(network common.Network, wallet string) ([]byte, bool) {
|
||||
return &chaincfg.MainNetParams
|
||||
case common.NetworkTestnet:
|
||||
return &chaincfg.TestNet3Params
|
||||
case common.NetworkFractalMainnet:
|
||||
return &chaincfg.MainNetParams
|
||||
case common.NetworkFractalTestnet:
|
||||
return &chaincfg.MainNetParams
|
||||
}
|
||||
panic("invalid network")
|
||||
}()
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package runes
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "v0.0.1"
|
||||
DBVersion = 1
|
||||
EventHashVersion = 1
|
||||
)
|
||||
|
||||
var startingBlockHeader = map[common.Network]types.BlockHeader{
|
||||
common.NetworkMainnet: {
|
||||
Height: 839999,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("0000000000000000000172014ba58d66455762add0512355ad651207918494ab")),
|
||||
PrevBlock: *utils.Must(chainhash.NewHashFromStr("00000000000000000001dcce6ce7c8a45872cafd1fb04732b447a14a91832591")),
|
||||
},
|
||||
common.NetworkTestnet: {
|
||||
Height: 2583200,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("000000000006c5f0dfcd9e0e81f27f97a87aef82087ffe69cd3c390325bb6541")),
|
||||
PrevBlock: *utils.Must(chainhash.NewHashFromStr("00000000000668f3bafac992f53424774515440cb47e1cb9e73af3f496139e28")),
|
||||
},
|
||||
}
|
||||
126
modules/runes/constants/constants.go
Normal file
126
modules/runes/constants/constants.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "v0.0.1"
|
||||
DBVersion = 1
|
||||
EventHashVersion = 1
|
||||
)
|
||||
|
||||
var StartingBlockHeader = map[common.Network]types.BlockHeader{
|
||||
common.NetworkMainnet: {
|
||||
Height: 839999,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("0000000000000000000172014ba58d66455762add0512355ad651207918494ab")),
|
||||
},
|
||||
common.NetworkTestnet: {
|
||||
Height: 2519999,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("000000000006f45c16402f05d9075db49d3571cf5273cf4cbeaa2aa295f7c833")),
|
||||
},
|
||||
common.NetworkFractalMainnet: {
|
||||
Height: 83999,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")), // TODO: Update this to match real hash
|
||||
},
|
||||
common.NetworkFractalTestnet: {
|
||||
Height: 83999,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("00000000000000613ddfbdd1778b17cea3818febcbbf82762eafaa9461038343")),
|
||||
},
|
||||
}
|
||||
|
||||
type GenesisRuneConfig struct {
|
||||
RuneId runes.RuneId
|
||||
Name string
|
||||
Number uint64
|
||||
Divisibility uint8
|
||||
Premine uint128.Uint128
|
||||
SpacedRune runes.SpacedRune
|
||||
Symbol rune
|
||||
Terms *runes.Terms
|
||||
Turbo bool
|
||||
EtchingTxHash chainhash.Hash
|
||||
EtchedAt time.Time
|
||||
}
|
||||
|
||||
var GenesisRuneConfigMap = map[common.Network]GenesisRuneConfig{
|
||||
common.NetworkMainnet: {
|
||||
RuneId: runes.RuneId{BlockHeight: 1, TxIndex: 0},
|
||||
Number: 0,
|
||||
Divisibility: 0,
|
||||
Premine: uint128.Zero,
|
||||
SpacedRune: runes.NewSpacedRune(runes.NewRune(2055900680524219742), 0b10000000),
|
||||
Symbol: '\u29c9',
|
||||
Terms: &runes.Terms{
|
||||
Amount: lo.ToPtr(uint128.From64(1)),
|
||||
Cap: &uint128.Max,
|
||||
HeightStart: lo.ToPtr(uint64(840000)),
|
||||
HeightEnd: lo.ToPtr(uint64(1050000)),
|
||||
OffsetStart: nil,
|
||||
OffsetEnd: nil,
|
||||
},
|
||||
Turbo: true,
|
||||
EtchingTxHash: chainhash.Hash{},
|
||||
EtchedAt: time.Time{},
|
||||
},
|
||||
common.NetworkFractalMainnet: {
|
||||
RuneId: runes.RuneId{BlockHeight: 1, TxIndex: 0},
|
||||
Number: 0,
|
||||
Divisibility: 0,
|
||||
Premine: uint128.Zero,
|
||||
SpacedRune: runes.NewSpacedRune(runes.NewRune(2055900680524219742), 0b10000000),
|
||||
Symbol: '\u29c9',
|
||||
Terms: &runes.Terms{
|
||||
Amount: lo.ToPtr(uint128.From64(1)),
|
||||
Cap: &uint128.Max,
|
||||
HeightStart: lo.ToPtr(uint64(84000)),
|
||||
HeightEnd: lo.ToPtr(uint64(2184000)),
|
||||
OffsetStart: nil,
|
||||
OffsetEnd: nil,
|
||||
},
|
||||
Turbo: true,
|
||||
EtchingTxHash: chainhash.Hash{},
|
||||
EtchedAt: time.Time{},
|
||||
},
|
||||
common.NetworkFractalTestnet: {
|
||||
RuneId: runes.RuneId{BlockHeight: 1, TxIndex: 0},
|
||||
Number: 0,
|
||||
Divisibility: 0,
|
||||
Premine: uint128.Zero,
|
||||
SpacedRune: runes.NewSpacedRune(runes.NewRune(2055900680524219742), 0b10000000),
|
||||
Symbol: '\u29c9',
|
||||
Terms: &runes.Terms{
|
||||
Amount: lo.ToPtr(uint128.From64(1)),
|
||||
Cap: &uint128.Max,
|
||||
HeightStart: lo.ToPtr(uint64(84000)),
|
||||
HeightEnd: lo.ToPtr(uint64(2184000)),
|
||||
OffsetStart: nil,
|
||||
OffsetEnd: nil,
|
||||
},
|
||||
Turbo: true,
|
||||
EtchingTxHash: chainhash.Hash{},
|
||||
EtchedAt: time.Time{},
|
||||
},
|
||||
}
|
||||
|
||||
func NetworkHasGenesisRune(network common.Network) bool {
|
||||
switch network {
|
||||
case common.NetworkMainnet, common.NetworkFractalMainnet, common.NetworkFractalTestnet:
|
||||
return true
|
||||
case common.NetworkTestnet:
|
||||
return false
|
||||
default:
|
||||
logger.Panic(fmt.Sprintf("unsupported network: %s", network))
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/constants"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
"github.com/gaze-network/uint128"
|
||||
@@ -28,7 +29,7 @@ func (p *Processor) calculateEventHash(header types.BlockHeader) (chainhash.Hash
|
||||
|
||||
func (p *Processor) getHashPayload(header types.BlockHeader) ([]byte, error) {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("payload:v" + strconv.Itoa(EventHashVersion) + ":")
|
||||
sb.WriteString("payload:v" + strconv.Itoa(constants.EventHashVersion) + ":")
|
||||
sb.WriteString("blockHash:")
|
||||
sb.Write(header.Hash[:])
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/constants"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/gaze-network/indexer-network/pkg/reportingclient"
|
||||
"github.com/gaze-network/uint128"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Make sure to implement the Bitcoin Processor interface
|
||||
@@ -68,8 +67,8 @@ func (p *Processor) VerifyStates(ctx context.Context) error {
|
||||
if err := p.ensureValidState(ctx); err != nil {
|
||||
return errors.Wrap(err, "error during ensureValidState")
|
||||
}
|
||||
if p.network == common.NetworkMainnet {
|
||||
if err := p.ensureGenesisRune(ctx); err != nil {
|
||||
if constants.NetworkHasGenesisRune(p.network) {
|
||||
if err := p.ensureGenesisRune(ctx, p.network); err != nil {
|
||||
return errors.Wrap(err, "error during ensureGenesisRune")
|
||||
}
|
||||
}
|
||||
@@ -89,17 +88,17 @@ func (p *Processor) ensureValidState(ctx context.Context) error {
|
||||
// if not found, set indexer state
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
if err := p.indexerInfoDg.SetIndexerState(ctx, entity.IndexerState{
|
||||
DBVersion: DBVersion,
|
||||
EventHashVersion: EventHashVersion,
|
||||
DBVersion: constants.DBVersion,
|
||||
EventHashVersion: constants.EventHashVersion,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "failed to set indexer state")
|
||||
}
|
||||
} else {
|
||||
if indexerState.DBVersion != DBVersion {
|
||||
return errors.Wrapf(errs.ConflictSetting, "db version mismatch: current version is %d. Please upgrade to version %d", indexerState.DBVersion, DBVersion)
|
||||
if indexerState.DBVersion != constants.DBVersion {
|
||||
return errors.Wrapf(errs.ConflictSetting, "db version mismatch: current version is %d. Please upgrade to version %d", indexerState.DBVersion, constants.DBVersion)
|
||||
}
|
||||
if indexerState.EventHashVersion != EventHashVersion {
|
||||
return errors.Wrapf(errs.ConflictSetting, "event version mismatch: current version is %d. Please reset rune's db first.", indexerState.EventHashVersion, EventHashVersion)
|
||||
if indexerState.EventHashVersion != constants.EventHashVersion {
|
||||
return errors.Wrapf(errs.ConflictSetting, "event version mismatch: current version is %d. Please reset rune's db first.", indexerState.EventHashVersion, constants.EventHashVersion)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,39 +118,34 @@ func (p *Processor) ensureValidState(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var genesisRuneId = runes.RuneId{BlockHeight: 1, TxIndex: 0}
|
||||
|
||||
func (p *Processor) ensureGenesisRune(ctx context.Context) error {
|
||||
_, err := p.runesDg.GetRuneEntryByRuneId(ctx, genesisRuneId)
|
||||
func (p *Processor) ensureGenesisRune(ctx context.Context, network common.Network) error {
|
||||
genesisRuneConfig, ok := constants.GenesisRuneConfigMap[network]
|
||||
if !ok {
|
||||
logger.Panic("genesis rune config not found", slogx.Stringer("network", network))
|
||||
}
|
||||
_, err := p.runesDg.GetRuneEntryByRuneId(ctx, genesisRuneConfig.RuneId)
|
||||
if err != nil && !errors.Is(err, errs.NotFound) {
|
||||
return errors.Wrap(err, "failed to get genesis rune entry")
|
||||
}
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
runeEntry := &runes.RuneEntry{
|
||||
RuneId: genesisRuneId,
|
||||
Number: 0,
|
||||
Divisibility: 0,
|
||||
Premine: uint128.Zero,
|
||||
SpacedRune: runes.NewSpacedRune(runes.NewRune(2055900680524219742), 0b10000000),
|
||||
Symbol: '\u29c9',
|
||||
Terms: &runes.Terms{
|
||||
Amount: lo.ToPtr(uint128.From64(1)),
|
||||
Cap: &uint128.Max,
|
||||
HeightStart: lo.ToPtr(uint64(common.HalvingInterval * 4)),
|
||||
HeightEnd: lo.ToPtr(uint64(common.HalvingInterval * 5)),
|
||||
OffsetStart: nil,
|
||||
OffsetEnd: nil,
|
||||
},
|
||||
Turbo: true,
|
||||
RuneId: genesisRuneConfig.RuneId,
|
||||
Number: genesisRuneConfig.Number,
|
||||
Divisibility: genesisRuneConfig.Divisibility,
|
||||
Premine: genesisRuneConfig.Premine,
|
||||
SpacedRune: genesisRuneConfig.SpacedRune,
|
||||
Symbol: genesisRuneConfig.Symbol,
|
||||
Terms: genesisRuneConfig.Terms,
|
||||
Turbo: genesisRuneConfig.Turbo,
|
||||
Mints: uint128.Zero,
|
||||
BurnedAmount: uint128.Zero,
|
||||
CompletedAt: time.Time{},
|
||||
CompletedAtHeight: nil,
|
||||
EtchingBlock: 1,
|
||||
EtchingTxHash: chainhash.Hash{},
|
||||
EtchedAt: time.Time{},
|
||||
EtchingBlock: genesisRuneConfig.RuneId.BlockHeight,
|
||||
EtchingTxHash: genesisRuneConfig.EtchingTxHash,
|
||||
EtchedAt: genesisRuneConfig.EtchedAt,
|
||||
}
|
||||
if err := p.runesDg.CreateRuneEntry(ctx, runeEntry, genesisRuneId.BlockHeight); err != nil {
|
||||
if err := p.runesDg.CreateRuneEntry(ctx, runeEntry, genesisRuneConfig.RuneId.BlockHeight); err != nil {
|
||||
return errors.Wrap(err, "failed to create genesis rune entry")
|
||||
}
|
||||
}
|
||||
@@ -166,7 +160,7 @@ func (p *Processor) CurrentBlock(ctx context.Context) (types.BlockHeader, error)
|
||||
blockHeader, err := p.runesDg.GetLatestBlock(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return startingBlockHeader[p.network], nil
|
||||
return constants.StartingBlockHeader[p.network], nil
|
||||
}
|
||||
return types.BlockHeader{}, errors.Wrap(err, "failed to get latest block")
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/constants"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/runes/runes"
|
||||
@@ -687,10 +688,10 @@ func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeade
|
||||
return errors.Wrap(err, "failed to calculate event hash")
|
||||
}
|
||||
prevIndexedBlock, err := runesDgTx.GetIndexedBlockByHeight(ctx, blockHeader.Height-1)
|
||||
if err != nil && errors.Is(err, errs.NotFound) && blockHeader.Height-1 == startingBlockHeader[p.network].Height {
|
||||
if err != nil && errors.Is(err, errs.NotFound) && blockHeader.Height-1 == constants.StartingBlockHeader[p.network].Height {
|
||||
prevIndexedBlock = &entity.IndexedBlock{
|
||||
Height: startingBlockHeader[p.network].Height,
|
||||
Hash: startingBlockHeader[p.network].Hash,
|
||||
Height: constants.StartingBlockHeader[p.network].Height,
|
||||
Hash: constants.StartingBlockHeader[p.network].Hash,
|
||||
EventHash: chainhash.Hash{},
|
||||
CumulativeEventHash: chainhash.Hash{},
|
||||
}
|
||||
@@ -791,9 +792,9 @@ func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeade
|
||||
if p.reportingClient != nil {
|
||||
if err := p.reportingClient.SubmitBlockReport(ctx, reportingclient.SubmitBlockReportPayload{
|
||||
Type: "runes",
|
||||
ClientVersion: Version,
|
||||
DBVersion: DBVersion,
|
||||
EventHashVersion: EventHashVersion,
|
||||
ClientVersion: constants.Version,
|
||||
DBVersion: constants.DBVersion,
|
||||
EventHashVersion: constants.EventHashVersion,
|
||||
Network: p.network,
|
||||
BlockHeight: uint64(blockHeader.Height),
|
||||
BlockHash: blockHeader.Hash,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/uint128"
|
||||
)
|
||||
|
||||
@@ -58,7 +59,8 @@ func ParseFlags(input interface{}) (Flags, error) {
|
||||
}
|
||||
return Flags(u128), nil
|
||||
default:
|
||||
panic("invalid flags input type")
|
||||
logger.Panic("invalid flags input type")
|
||||
return Flags{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package runes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/uint128"
|
||||
)
|
||||
|
||||
@@ -119,20 +121,25 @@ func (r Rune) Cmp(other Rune) int {
|
||||
func FirstRuneHeight(network common.Network) uint64 {
|
||||
switch network {
|
||||
case common.NetworkMainnet:
|
||||
return common.HalvingInterval * 4
|
||||
return 840_000
|
||||
case common.NetworkTestnet:
|
||||
return common.HalvingInterval * 12
|
||||
return 2_520_000
|
||||
case common.NetworkFractalMainnet:
|
||||
return 84_000
|
||||
case common.NetworkFractalTestnet:
|
||||
return 84_000
|
||||
}
|
||||
panic("invalid network")
|
||||
logger.Panic(fmt.Sprintf("invalid network: %s", network))
|
||||
return 0
|
||||
}
|
||||
|
||||
func MinimumRuneAtHeight(network common.Network, height uint64) Rune {
|
||||
offset := height + 1
|
||||
interval := common.HalvingInterval / 12
|
||||
interval := network.HalvingInterval() / 12
|
||||
|
||||
// runes are gradually unlocked from rune activation height until the next halving
|
||||
start := FirstRuneHeight(network)
|
||||
end := start + common.HalvingInterval
|
||||
end := start + network.HalvingInterval()
|
||||
|
||||
if offset < start {
|
||||
return (Rune)(unlockSteps[12])
|
||||
|
||||
@@ -92,8 +92,8 @@ func TestMinimumRuneAtHeightMainnet(t *testing.T) {
|
||||
}
|
||||
|
||||
start := FirstRuneHeight(common.NetworkMainnet)
|
||||
end := start + common.HalvingInterval
|
||||
interval := uint64(common.HalvingInterval / 12)
|
||||
end := start + common.NetworkMainnet.HalvingInterval()
|
||||
interval := uint64(common.NetworkMainnet.HalvingInterval() / 12)
|
||||
|
||||
test(0, "AAAAAAAAAAAAA")
|
||||
test(start/2, "AAAAAAAAAAAAA")
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/uint128"
|
||||
)
|
||||
|
||||
@@ -102,6 +103,7 @@ func ParseTag(input interface{}) (Tag, error) {
|
||||
}
|
||||
return Tag(u128), nil
|
||||
default:
|
||||
panic("invalid tag input type")
|
||||
logger.Panic("invalid tag input type")
|
||||
return Tag{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,18 @@ func (a Address) Equal(b Address) bool {
|
||||
return a.encoded == b.encoded
|
||||
}
|
||||
|
||||
// DustLimit returns the output dust limit (lowest possible satoshis in a UTXO) for the address type.
|
||||
func (a Address) DustLimit() int64 {
|
||||
switch a.encodedType {
|
||||
case AddressP2TR:
|
||||
return 330
|
||||
case AddressP2WPKH:
|
||||
return 294
|
||||
default:
|
||||
return 546
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (a Address) MarshalText() ([]byte, error) {
|
||||
return []byte(a.encoded), nil
|
||||
|
||||
@@ -447,3 +447,72 @@ func TestAddressPkScript(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddressDustLimit(t *testing.T) {
|
||||
type Spec struct {
|
||||
Address string
|
||||
DefaultNet *chaincfg.Params
|
||||
ExpectedDustLimit int64
|
||||
}
|
||||
|
||||
specs := []Spec{
|
||||
{
|
||||
Address: "bc1qfpgdxtpl7kz5qdus2pmexyjaza99c28q8uyczh",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 294,
|
||||
},
|
||||
{
|
||||
Address: "tb1qfpgdxtpl7kz5qdus2pmexyjaza99c28qd6ltey",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 294,
|
||||
},
|
||||
{
|
||||
Address: "bc1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qvz5d38",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 330,
|
||||
},
|
||||
{
|
||||
Address: "tb1p7h87kqsmpzatddzhdhuy9gmxdpvn5kvar6hhqlgau8d2ffa0pa3qm2zztg",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 330,
|
||||
},
|
||||
{
|
||||
Address: "3Ccte7SJz71tcssLPZy3TdWz5DTPeNRbPw",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 546,
|
||||
},
|
||||
{
|
||||
Address: "1KrRZSShVkdc8J71CtY4wdw46Rx3BRLKyH",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 546,
|
||||
},
|
||||
{
|
||||
Address: "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 546,
|
||||
},
|
||||
{
|
||||
Address: "migbBPcDajPfffrhoLpYFTQNXQFbWbhpz3",
|
||||
DefaultNet: &chaincfg.TestNet3Params,
|
||||
ExpectedDustLimit: 546,
|
||||
},
|
||||
{
|
||||
Address: "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7",
|
||||
DefaultNet: &chaincfg.MainNetParams,
|
||||
ExpectedDustLimit: 546,
|
||||
},
|
||||
{
|
||||
Address: "2NCxMvHPTduZcCuUeAiWUpuwHga7Y66y9XJ",
|
||||
DefaultNet: &chaincfg.TestNet3Params,
|
||||
ExpectedDustLimit: 546,
|
||||
},
|
||||
}
|
||||
|
||||
for _, spec := range specs {
|
||||
t.Run(spec.Address, func(t *testing.T) {
|
||||
addr, err := btcutils.SafeNewAddress(spec.Address, spec.DefaultNet)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, spec.ExpectedDustLimit, addr.DustLimit())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
56
pkg/btcutils/fee.go
Normal file
56
pkg/btcutils/fee.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package btcutils
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
)
|
||||
|
||||
// EstimateSignedTxNetworkFee estimates the network fee for the given transaction. "prevTxOuts" should be list of all outputs used as inputs in the transaction.
|
||||
// If the transaction has unsigned inputs, the fee will be calculated as if those inputs were signed.
|
||||
func EstimateSignedTxNetworkFee(tx *wire.MsgTx, prevTxOuts []*wire.TxOut, feeRate int64) (int64, error) {
|
||||
if len(tx.TxIn) != len(prevTxOuts) {
|
||||
return 0, errors.Wrapf(errs.InvalidArgument, "tx.TxIn length (%d) must match prevTxOuts length (%d)", len(tx.TxIn), len(prevTxOuts))
|
||||
}
|
||||
tx = tx.Copy()
|
||||
mockPrivateKey, _ := btcec.NewPrivateKey()
|
||||
for i := range tx.TxIn {
|
||||
if len(tx.TxIn[i].SignatureScript) > 0 || (len(tx.TxIn[i].Witness) > 0 && len(tx.TxIn[i].Witness[0]) > 0) {
|
||||
// already signed, skip
|
||||
continue
|
||||
}
|
||||
address, err := ExtractAddressFromPkScript(prevTxOuts[i].PkScript)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to extract address from pkScript %d", i)
|
||||
}
|
||||
// if the input is a taproot script-path spend, we need to sign it with the tapscript
|
||||
if address.Type() == AddressTaproot && len(tx.TxIn[i].Witness) == 3 {
|
||||
tx, err = SignTxInputTapScript(tx, mockPrivateKey, prevTxOuts[i], i)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to sign tx input %d (tapscript)", i)
|
||||
}
|
||||
} else {
|
||||
tx, err = SignTxInput(tx, mockPrivateKey, prevTxOuts[i], i)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to sign tx input %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
|
||||
txVBytes := calVBytes(txWeight)
|
||||
|
||||
fee := txVBytes * feeRate
|
||||
return fee, nil
|
||||
}
|
||||
|
||||
func calVBytes(txWeight int64) int64 {
|
||||
// VBytes = txWeight/4, a fraction of Vbyte uses 1 Vbyte.
|
||||
txVBytes := txWeight / 4
|
||||
if txWeight%4 > 0 {
|
||||
txVBytes += 1
|
||||
}
|
||||
return txVBytes
|
||||
}
|
||||
@@ -3,8 +3,12 @@ package btcutils
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
verifier "github.com/bitonicnl/verify-signed-message/pkg"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
)
|
||||
|
||||
func VerifySignature(address string, message string, sigBase64 string, defaultNet ...*chaincfg.Params) error {
|
||||
@@ -19,3 +23,121 @@ func VerifySignature(address string, message string, sigBase64 string, defaultNe
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SignTxInput(tx *wire.MsgTx, privateKey *btcec.PrivateKey, prevTxOut *wire.TxOut, inputIndex int) (*wire.MsgTx, error) {
|
||||
if privateKey == nil {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "PrivateKey is required")
|
||||
}
|
||||
if tx == nil {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "Tx is required")
|
||||
}
|
||||
if prevTxOut == nil {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "PrevTxOut is required")
|
||||
}
|
||||
|
||||
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(prevTxOut.PkScript, prevTxOut.Value)
|
||||
sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher)
|
||||
if len(tx.TxIn) <= inputIndex {
|
||||
return nil, errors.Errorf("input to sign (%d) is out of range", inputIndex)
|
||||
}
|
||||
address, err := ExtractAddressFromPkScript(prevTxOut.PkScript)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to extract address")
|
||||
}
|
||||
|
||||
switch address.Type() {
|
||||
case AddressP2TR:
|
||||
witness, err := txscript.TaprootWitnessSignature(
|
||||
tx,
|
||||
sigHashes,
|
||||
inputIndex,
|
||||
prevTxOut.Value,
|
||||
prevTxOut.PkScript,
|
||||
txscript.SigHashAll|txscript.SigHashAnyOneCanPay,
|
||||
privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign")
|
||||
}
|
||||
tx.TxIn[inputIndex].Witness = witness
|
||||
case AddressP2WPKH:
|
||||
witness, err := txscript.WitnessSignature(
|
||||
tx,
|
||||
sigHashes,
|
||||
inputIndex,
|
||||
prevTxOut.Value,
|
||||
prevTxOut.PkScript,
|
||||
txscript.SigHashAll|txscript.SigHashAnyOneCanPay,
|
||||
privateKey,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign")
|
||||
}
|
||||
tx.TxIn[inputIndex].Witness = witness
|
||||
case AddressP2PKH:
|
||||
sigScript, err := txscript.SignatureScript(
|
||||
tx,
|
||||
inputIndex,
|
||||
prevTxOut.PkScript,
|
||||
txscript.SigHashAll|txscript.SigHashAnyOneCanPay,
|
||||
privateKey,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign")
|
||||
}
|
||||
tx.TxIn[inputIndex].SignatureScript = sigScript
|
||||
default:
|
||||
return nil, errors.Wrapf(errs.NotSupported, "unsupported input address type %s", address.Type())
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func SignTxInputTapScript(tx *wire.MsgTx, privateKey *btcec.PrivateKey, prevTxOut *wire.TxOut, inputIndex int) (*wire.MsgTx, error) {
|
||||
if privateKey == nil {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "PrivateKey is required")
|
||||
}
|
||||
if tx == nil {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "Tx is required")
|
||||
}
|
||||
if prevTxOut == nil {
|
||||
return nil, errors.Wrap(errs.InvalidArgument, "PrevTxOut is required")
|
||||
}
|
||||
|
||||
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(prevTxOut.PkScript, prevTxOut.Value)
|
||||
sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher)
|
||||
if len(tx.TxIn) <= inputIndex {
|
||||
return nil, errors.Errorf("input to sign (%d) is out of range", inputIndex)
|
||||
}
|
||||
address, err := ExtractAddressFromPkScript(prevTxOut.PkScript)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to extract address")
|
||||
}
|
||||
|
||||
if address.Type() != AddressTaproot {
|
||||
return nil, errors.Errorf("input type must be %s", AddressTaproot)
|
||||
}
|
||||
|
||||
witness := tx.TxIn[inputIndex].Witness
|
||||
if len(witness) != 3 {
|
||||
return nil, errors.Wrapf(errs.InvalidArgument, "invalid witness length: expected 3, got %d", len(witness))
|
||||
}
|
||||
|
||||
tapLeaf := txscript.NewBaseTapLeaf(witness[1])
|
||||
signature, err := txscript.RawTxInTapscriptSignature(
|
||||
tx,
|
||||
sigHashes,
|
||||
inputIndex,
|
||||
prevTxOut.Value,
|
||||
prevTxOut.PkScript,
|
||||
tapLeaf,
|
||||
txscript.SigHashAll|txscript.SigHashAnyOneCanPay,
|
||||
privateKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to sign")
|
||||
}
|
||||
tx.TxIn[inputIndex].Witness[0] = signature
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
@@ -3,8 +3,14 @@ package btcutils
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestVerifySignature(t *testing.T) {
|
||||
@@ -67,3 +73,115 @@ func TestVerifySignature(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignTxInput(t *testing.T) {
|
||||
generateTxAndPrevTxOutFromPkScript := func(pkScript []byte) (*wire.MsgTx, *wire.TxOut) {
|
||||
tx := wire.NewMsgTx(wire.TxVersion)
|
||||
tx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Index: 1,
|
||||
},
|
||||
})
|
||||
txOut := &wire.TxOut{
|
||||
Value: 1e8, PkScript: pkScript,
|
||||
}
|
||||
tx.AddTxOut(txOut)
|
||||
// using same value and pkScript as input for simplicity
|
||||
return tx, txOut
|
||||
}
|
||||
verifySignedTx := func(t *testing.T, signedTx *wire.MsgTx, prevTxOut *wire.TxOut) {
|
||||
t.Helper()
|
||||
|
||||
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(prevTxOut.PkScript, prevTxOut.Value)
|
||||
sigHashes := txscript.NewTxSigHashes(signedTx, prevOutFetcher)
|
||||
vm, err := txscript.NewEngine(
|
||||
prevTxOut.PkScript, signedTx, 0, txscript.StandardVerifyFlags,
|
||||
nil, sigHashes, prevTxOut.Value, prevOutFetcher,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, vm.Execute(), "error during signature verification") // no error means success
|
||||
}
|
||||
|
||||
privKey, _ := btcec.NewPrivateKey()
|
||||
t.Run("P2TR input", func(t *testing.T) {
|
||||
taprootKey := txscript.ComputeTaprootKeyNoScript(privKey.PubKey())
|
||||
|
||||
pkScript, err := txscript.PayToTaprootScript(taprootKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx, prevTxOut := generateTxAndPrevTxOutFromPkScript(pkScript)
|
||||
signedTx, err := SignTxInput(
|
||||
tx, privKey, prevTxOut, 0,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
verifySignedTx(t, signedTx, prevTxOut)
|
||||
})
|
||||
t.Run("tapscript input", func(t *testing.T) {
|
||||
internalKey := privKey.PubKey()
|
||||
|
||||
// Our script will be a simple OP_CHECKSIG as the sole leaf of a
|
||||
// tapscript tree.
|
||||
builder := txscript.NewScriptBuilder()
|
||||
builder.AddData(schnorr.SerializePubKey(internalKey))
|
||||
builder.AddOp(txscript.OP_CHECKSIG)
|
||||
tapScript, err := builder.Script()
|
||||
require.NoError(t, err)
|
||||
|
||||
tapLeaf := txscript.NewBaseTapLeaf(tapScript)
|
||||
tapScriptTree := txscript.AssembleTaprootScriptTree(tapLeaf)
|
||||
|
||||
controlBlock := tapScriptTree.LeafMerkleProofs[0].ToControlBlock(
|
||||
internalKey,
|
||||
)
|
||||
controlBlockBytes, err := controlBlock.ToBytes()
|
||||
require.NoError(t, err)
|
||||
|
||||
tapScriptRootHash := tapScriptTree.RootNode.TapHash()
|
||||
outputKey := txscript.ComputeTaprootOutputKey(
|
||||
internalKey, tapScriptRootHash[:],
|
||||
)
|
||||
p2trScript, err := txscript.PayToTaprootScript(outputKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx, prevTxOut := generateTxAndPrevTxOutFromPkScript(p2trScript)
|
||||
tx.TxIn[0].Witness = wire.TxWitness{
|
||||
{},
|
||||
tapScript,
|
||||
controlBlockBytes,
|
||||
}
|
||||
signedTx, err := SignTxInputTapScript(
|
||||
tx, privKey, prevTxOut, 0,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
verifySignedTx(t, signedTx, prevTxOut)
|
||||
})
|
||||
t.Run("P2WPKH input", func(t *testing.T) {
|
||||
pubKey := privKey.PubKey()
|
||||
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
|
||||
|
||||
pkScript, err := txscript.NewScriptBuilder().
|
||||
AddOp(txscript.OP_0).
|
||||
AddData(pubKeyHash).
|
||||
Script()
|
||||
|
||||
tx, prevTxOut := generateTxAndPrevTxOutFromPkScript(pkScript)
|
||||
signedTx, err := SignTxInput(
|
||||
tx, privKey, prevTxOut, 0,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
verifySignedTx(t, signedTx, prevTxOut)
|
||||
})
|
||||
t.Run("P2PKH input", func(t *testing.T) {
|
||||
pubKey := privKey.PubKey()
|
||||
pubKeyHash := btcutil.Hash160(pubKey.SerializeCompressed())
|
||||
address, err := btcutil.NewAddressPubKeyHash(pubKeyHash, &chaincfg.MainNetParams)
|
||||
pkScript, err := txscript.PayToAddrScript(address)
|
||||
|
||||
tx, prevTxOut := generateTxAndPrevTxOutFromPkScript(pkScript)
|
||||
signedTx, err := SignTxInput(
|
||||
tx, privKey, prevTxOut, 0,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
verifySignedTx(t, signedTx, prevTxOut)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user