<commit_before>package bitcoind

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"errors"
	"github.com/OpenBazaar/spvwallet"
	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcd/btcjson"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcrpcclient"
	btc "github.com/btcsuite/btcutil"
	hd "github.com/btcsuite/btcutil/hdkeychain"
	"github.com/btcsuite/btcutil/txsort"
	"github.com/op/go-logging"
	b39 "github.com/tyler-smith/go-bip39"
	"io/ioutil"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"
)

var log = logging.MustGetLogger("bitcoind")

const FlagPrefix = 0x00

type BitcoindWallet struct {
	params           *chaincfg.Params
	repoPath         string
	trustedPeer      string
	masterPrivateKey *hd.ExtendedKey
	masterPublicKey  *hd.ExtendedKey
	listeners        []func(spvwallet.TransactionCallback)
	rpcClient        *btcrpcclient.Client
	binary           string
}

var connCfg *btcrpcclient.ConnConfig = &btcrpcclient.ConnConfig{
	Host:                 "localhost:8332",
	HTTPPostMode:         true, // Bitcoin core only supports HTTP POST mode
	DisableTLS:           true, // Bitcoin core does not provide TLS by default
	DisableAutoReconnect: false,
	DisableConnectOnNew:  false,
}

func NewBitcoindWallet(mnemonic string, params *chaincfg.Params, repoPath string, trustedPeer string, binary string, username string, password string) *BitcoindWallet {
	seed := b39.NewSeed(mnemonic, "")
	mPrivKey, _ := hd.NewMaster(seed, params)
	mPubKey, _ := mPrivKey.Neuter()

	if params.Name == chaincfg.TestNet3Params.Name || params.Name == chaincfg.RegressionNetParams.Name {
		connCfg.Host = "localhost:18332"
	}

	connCfg.User = username
	connCfg.Pass = password

	// TODO: need to make a similar script for windows
	script := []byte("#!/bin/bash\ncurl -d $1 http://localhost:8330/")
	ioutil.WriteFile(path.Join(repoPath, "notify.sh"), script, 0777)

	if trustedPeer != "" {
		trustedPeer = strings.Split(trustedPeer, ":")[0]
	}

	w := BitcoindWallet{
		params:           params,
		repoPath:         repoPath,
		trustedPeer:      trustedPeer,
		masterPrivateKey: mPrivKey,
		masterPublicKey:  mPubKey,
		binary:           binary,
	}
	return &w
}

func (w *BitcoindWallet) Start() {
	w.shutdownIfActive()
	args := []string{"-walletnotify='" + path.Join(w.repoPath, "notify.sh") + " %s'", "-server"}
	if w.params.Name == chaincfg.TestNet3Params.Name {
		args = append(args, "-testnet")
	} else if w.params.Name == chaincfg.RegressionNetParams.Name {
		args = append(args, "-regtest")
	}
	if w.trustedPeer != "" {
		args = append(args, "-connect="+w.trustedPeer)
	}
	client, _ := btcrpcclient.New(connCfg, nil)
	w.rpcClient = client
	go startNotificationListener(client, w.listeners)

	cmd := exec.Command(w.binary, args...)
	cmd.Start()
	ticker := time.NewTicker(20 * time.Second)
	go func() {
		for range ticker.C {
			log.Fatal("Failed to connect to bitcoind")
		}
	}()
	for {
		_, err := client.GetBlockCount()
		if err == nil {
			break
		}
	}
	ticker.Stop()
	log.Info("Connected to bitcoind")
}

// If bitcoind is already running let's shut it down so we restart it with our options
func (w *BitcoindWallet) shutdownIfActive() {
	client, err := btcrpcclient.New(connCfg, nil)
	if err != nil {
		return
	}
	client.RawRequest("stop", []json.RawMessage{})
	client.Shutdown()
	time.Sleep(5 * time.Second)
}

func (w *BitcoindWallet) CurrencyCode() string {
	return "btc"
}

func (w *BitcoindWallet) AcceptStealth() bool {
	return false
}

func (w *BitcoindWallet) MasterPrivateKey() *hd.ExtendedKey {
	return w.masterPrivateKey
}

func (w *BitcoindWallet) MasterPublicKey() *hd.ExtendedKey {
	return w.masterPublicKey
}

func (w *BitcoindWallet) CurrentAddress(purpose spvwallet.KeyPurpose) btc.Address {
	addr, _ := w.rpcClient.GetAccountAddress("")
	return addr
}

func (w *BitcoindWallet) HasKey(addr btc.Address) bool {
	_, err := w.rpcClient.DumpPrivKey(addr)
	if err != nil {
		return false
	}
	return true
}

func (w *BitcoindWallet) Balance() (confirmed, unconfirmed int64) {
	u, _ := w.rpcClient.GetUnconfirmedBalance("")
	c, _ := w.rpcClient.GetBalance("")
	return int64(c.ToUnit(btc.AmountSatoshi)), int64(u.ToUnit(btc.AmountSatoshi))
}

func (w *BitcoindWallet) ChainTip() uint32 {
	info, err := w.rpcClient.GetInfo()
	if err != nil {
		return uint32(0)
	}
	return uint32(info.Blocks)
}

func (w *BitcoindWallet) Spend(amount int64, addr btc.Address, feeLevel spvwallet.FeeLevel) error {
	amt, err := btc.NewAmount(float64(amount) / 100000000)
	if err != nil {
		return err
	}
	_, err = w.rpcClient.SendFrom("", addr, amt)
	return err
}

func (w *BitcoindWallet) SendStealth(amount int64, pubkey *btcec.PublicKey, feeLevel spvwallet.FeeLevel) error {
	// Generated ephemeral key pair
	ephemPriv, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return err
	}

	// Calculate a shared secret using the master private key and ephemeral public key
	ss := btcec.GenerateSharedSecret(ephemPriv, pubkey)

	// Create an HD key using the shared secret as the chaincode
	hdKey := hd.NewExtendedKey(
		w.params.HDPublicKeyID[:],
		pubkey.SerializeCompressed(),
		ss,
		[]byte{0x00, 0x00, 0x00, 0x00},
		0,
		0,
		false)

	// Derive child key 0
	childKey, err := hdKey.Child(0)
	if err != nil {
		return err<commit_msg>Fix getbalance for bitcoind impl<commit_after>package bitcoind

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"errors"
	"github.com/OpenBazaar/spvwallet"
	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcd/btcjson"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcrpcclient"
	btc "github.com/btcsuite/btcutil"
	hd "github.com/btcsuite/btcutil/hdkeychain"
	"github.com/btcsuite/btcutil/txsort"
	"github.com/op/go-logging"
	b39 "github.com/tyler-smith/go-bip39"
	"io/ioutil"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"
)

var log = logging.MustGetLogger("bitcoind")

const (
	FlagPrefix = 0x00
	Account    = "OpenBazaar"
)

type BitcoindWallet struct {
	params           *chaincfg.Params
	repoPath         string
	trustedPeer      string
	masterPrivateKey *hd.ExtendedKey
	masterPublicKey  *hd.ExtendedKey
	listeners        []func(spvwallet.TransactionCallback)
	rpcClient        *btcrpcclient.Client
	binary           string
}

var connCfg *btcrpcclient.ConnConfig = &btcrpcclient.ConnConfig{
	Host:                 "localhost:8332",
	HTTPPostMode:         true, // Bitcoin core only supports HTTP POST mode
	DisableTLS:           true, // Bitcoin core does not provide TLS by default
	DisableAutoReconnect: false,
	DisableConnectOnNew:  false,
}

func NewBitcoindWallet(mnemonic string, params *chaincfg.Params, repoPath string, trustedPeer string, binary string, username string, password string) *BitcoindWallet {
	seed := b39.NewSeed(mnemonic, "")
	mPrivKey, _ := hd.NewMaster(seed, params)
	mPubKey, _ := mPrivKey.Neuter()

	if params.Name == chaincfg.TestNet3Params.Name || params.Name == chaincfg.RegressionNetParams.Name {
		connCfg.Host = "localhost:18332"
	}

	connCfg.User = username
	connCfg.Pass = password

	// TODO: need to make a similar script for windows
	script := []byte("#!/bin/bash\ncurl -d $1 http://localhost:8330/")
	ioutil.WriteFile(path.Join(repoPath, "notify.sh"), script, 0777)

	if trustedPeer != "" {
		trustedPeer = strings.Split(trustedPeer, ":")[0]
	}

	w := BitcoindWallet{
		params:           params,
		repoPath:         repoPath,
		trustedPeer:      trustedPeer,
		masterPrivateKey: mPrivKey,
		masterPublicKey:  mPubKey,
		binary:           binary,
	}
	return &w
}

func (w *BitcoindWallet) Start() {
	w.shutdownIfActive()
	args := []string{"-walletnotify='" + path.Join(w.repoPath, "notify.sh") + " %s'", "-server"}
	if w.params.Name == chaincfg.TestNet3Params.Name {
		args = append(args, "-testnet")
	} else if w.params.Name == chaincfg.RegressionNetParams.Name {
		args = append(args, "-regtest")
	}
	if w.trustedPeer != "" {
		args = append(args, "-connect="+w.trustedPeer)
	}
	client, _ := btcrpcclient.New(connCfg, nil)
	w.rpcClient = client
	go startNotificationListener(client, w.listeners)

	cmd := exec.Command(w.binary, args...)
	cmd.Start()
	ticker := time.NewTicker(20 * time.Second)
	go func() {
		for range ticker.C {
			log.Fatal("Failed to connect to bitcoind")
		}
	}()
	for {
		_, err := client.GetBlockCount()
		if err == nil {
			break
		}
	}
	ticker.Stop()
	log.Info("Connected to bitcoind")
}

// If bitcoind is already running let's shut it down so we restart it with our options
func (w *BitcoindWallet) shutdownIfActive() {
	client, err := btcrpcclient.New(connCfg, nil)
	if err != nil {
		return
	}
	client.RawRequest("stop", []json.RawMessage{})
	client.Shutdown()
	time.Sleep(5 * time.Second)
}

func (w *BitcoindWallet) CurrencyCode() string {
	return "btc"
}

func (w *BitcoindWallet) AcceptStealth() bool {
	return false
}

func (w *BitcoindWallet) MasterPrivateKey() *hd.ExtendedKey {
	return w.masterPrivateKey
}

func (w *BitcoindWallet) MasterPublicKey() *hd.ExtendedKey {
	return w.masterPublicKey
}

func (w *BitcoindWallet) CurrentAddress(purpose spvwallet.KeyPurpose) btc.Address {
	addr, _ := w.rpcClient.GetAccountAddress(Account)
	return addr
}

func (w *BitcoindWallet) HasKey(addr btc.Address) bool {
	_, err := w.rpcClient.DumpPrivKey(addr)
	if err != nil {
		return false
	}
	return true
}

func (w *BitcoindWallet) Balance() (confirmed, unconfirmed int64) {
	resp, _ := w.rpcClient.RawRequest("getwalletinfo", []json.RawMessage{})
	type walletInfo struct {
		Balance     float64 `json:"balance"`
		Unconfirmed float64 `json:"unconfirmed_balance"`
	}
	respBytes, _ := resp.MarshalJSON()
	i := new(walletInfo)
	json.Unmarshal(respBytes, i)
	c, _ := btc.NewAmount(i.Balance)
	u, _ := btc.NewAmount(i.Unconfirmed)
	return int64(c.ToUnit(btc.AmountSatoshi)), int64(u.ToUnit(btc.AmountSatoshi))
}

func (w *BitcoindWallet) ChainTip() uint32 {
	info, err := w.rpcClient.GetInfo()
	if err != nil {
		return uint32(0)
	}
	return uint32(info.Blocks)
}

func (w *BitcoindWallet) Spend(amount int64, addr btc.Address, feeLevel spvwallet.FeeLevel) error {
	amt, err := btc.NewAmount(float64(amount) / 100000000)
	if err != nil {
		return err
	}
	_, err = w.rpcClient.SendFrom(Account, addr, amt)
	return err
}

func (w *BitcoindWallet) SendStealth(amount int64, pubkey *btcec.PublicKey, feeLevel spvwallet.FeeLevel) error {
	// Generated ephemeral key pair
	ephemPriv, err := btcec.NewPrivateKey(btcec.S256())
	if err != nil {
		return err
	}

	// Calculate a shared secret using the master private key and ephemeral public key
	ss := btcec.GenerateSharedSecret(ephemPriv, pubkey)

	// Create an HD key using the shared secret as the chaincode
	hdKey := hd.NewExtendedKey(
		w.params.HDPublicKeyID[:],
		pubkey.SerializeCompressed(),
		ss,
		[]byte{0x00, 0x00, 0x00, 0x00},
		0,
		0,
		false)

	// Derive child key 0
	childKey, err := hdKey.Child(0)
	if err != nil {
		return err
{'fraction_non_alphanumeric': 0.13238215640238396, 'fraction_numerical': 0.01480946360845223, 'mean_word_length': 4.059387848332572, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '327446', 'n_tokens_mistral': 4275, 'n_tokens_neox': 4109, 'n_words': 891}