Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Bitcoin SPV wallet #200

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added decred/decred/btc/__init__.py
Empty file.
828 changes: 828 additions & 0 deletions decred/decred/btc/addrlib.py

Large diffs are not rendered by default.

Empty file.
851 changes: 851 additions & 0 deletions decred/decred/btc/btcwallet/btcwallet.py

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions decred/decred/btc/btcwallet/golink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import atexit
import ctypes
import json
import logging
import os
import platform
from typing import Any, Dict, List, Union, Callable

log = logging.getLogger("GOBRIDGE")

JSONType = Union[str, int, float, bool, None, Dict[str, Any], List[Any]]

FileDir = os.path.dirname(os.path.realpath(__file__))

WINDOWS = platform.system() == "Windows"
LIB_EXT = "dll" if WINDOWS else "so"

devLibPath = os.path.join(FileDir, "libbtcwallet", "libbtcwallet."+LIB_EXT)

if os.path.isfile(devLibPath):
lib = ctypes.cdll.LoadLibrary(devLibPath)
else:
lib = ctypes.cdll.LoadLibrary("libbtcwallet."+LIB_EXT)


goFreeCharPtr = lib.FreeCharPtr
goFreeCharPtr.argtypes = [ctypes.c_char_p]

goCall = lib.Call
goCall.restype = ctypes.c_void_p
goCall.argtypes = [ctypes.c_char_p]


def Go(funcName: str, params: JSONType) -> JSONType:
b = GoRaw(funcName, params)
return json.loads(b or 'true') # empty response indicates success


def GoRaw(funcName: str, params: JSONType) -> bytes:
b = json.dumps(dict(
function=funcName,
params=params,
))
r = goCall(b.encode("utf-8"))
try:
return ctypes.cast(r, ctypes.c_char_p).value
except Exception as e:
log.error("Go error: %s", e)
finally:
goFreeCharPtr(ctypes.cast(r, ctypes.c_char_p))


def delink():
Go("exit", "")


feeders = []

logFeedID = 0


def registerFeeder(f: Callable[[bytes], None]):
feeders.append(f)


@ctypes.CFUNCTYPE(None, ctypes.c_char_p)
def feedRelay(msgB: bytes):
msg = json.loads(msgB)
feedID = msg["feedID"]
if feedID == logFeedID:
log.info(msg["payload"])
return
for feeder in feeders:
feeder(msg)


lib.Feed(feedRelay)

# Extra 'exit' calls are free, so call it prodigiously.
atexit.register(delink)
1 change: 1 addition & 0 deletions decred/decred/btc/btcwallet/libbtcwallet/build-nix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go build -buildmode=c-shared -o libbtcwallet.so
1 change: 1 addition & 0 deletions decred/decred/btc/btcwallet/libbtcwallet/build-win.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go build -buildmode=c-shared -o libbtcwallet.dll
15 changes: 15 additions & 0 deletions decred/decred/btc/btcwallet/libbtcwallet/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module example.com/libbtcwallet

go 1.13

require (
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/btcsuite/btcwallet v0.11.1-0.20210217230627-39cbb7bdd98a
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0
github.com/btcsuite/btcwallet/walletdb v1.3.4
github.com/btcsuite/btcwallet/wtxmgr v1.2.0
github.com/decred/dcrd/chaincfg/v3 v3.0.0
github.com/lightninglabs/neutrino v0.11.0
)
371 changes: 371 additions & 0 deletions decred/decred/btc/btcwallet/libbtcwallet/go.sum

Large diffs are not rendered by default.

196 changes: 196 additions & 0 deletions decred/decred/btc/btcwallet/libbtcwallet/golink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package main

/*
#include <stdlib.h>

typedef void (*pyfunc) (char *);

static inline void call_py_func(pyfunc ptr, char *b) {
(ptr)(b);
}
*/
import "C"
import (
"encoding/json"
"fmt"
"os"
"sync"
"sync/atomic"
"unsafe"

"github.com/btcsuite/btclog"
)

// CallData is the type sent for all golink calls.
type CallData struct {
Function string `json:"function"`
Params json.RawMessage `json:"params"`
}

func callError(s string, a ...interface{}) *C.char {
b, _ := json.Marshal(&struct {
Error string `json:"error"`
}{
Error: fmt.Sprintf(s, a...),
})
return C.CString(string(b))
}

var (
wllt *Wallet
wlltMtx sync.RWMutex
)

func theWallet() *Wallet {
wlltMtx.RLock()
defer wlltMtx.RUnlock()
return wllt
}

var utilityFuncs = map[string]walletRouter{
"walletExists": walletExistsUtility,
"createWallet": createWalletUtility,
"init": initUtility,
"exit": exitUtility,
}

// Call is used to invoke a registered function.
//export Call
func Call(msg *C.char, msgLen C.int) *C.char {
jsonStr := C.GoString(msg)
cd := new(CallData)
err := json.Unmarshal([]byte(jsonStr), cd)
if err != nil {
return callError("json Unmarshal error: %v", err)
}

f, ok := utilityFuncs[cd.Function]
if ok {
s, err := f(cd.Params)
if err != nil {
return callError("%s error: %v", cd.Function, err)
}
return C.CString(s)
}

w := theWallet()

if w == nil {
return callError("wallet not initialized")
}

f = w.router(cd.Function)
if f == nil {
return callError("no function %q", cd.Function)
}
s, err := f(cd.Params)
if err != nil {
return callError("%s error: %v", cd.Function, err)
}
return C.CString(s)
}

var feeders = map[C.pyfunc]struct{}{}

// Feed allows the user to subscribe a function to receive asynchronous
// updates like log messages and streaming notifications.
//export Feed
func Feed(fn C.pyfunc) {
if theWallet() != nil {
panic("do not register golink Feed after the wallet is initialized")
}
feeders[fn] = struct{}{}
}

// FreeCharPtr frees the memory associated with a *C.char.
//export FreeCharPtr
func FreeCharPtr(b *C.char) {
C.free(unsafe.Pointer(b))
}

const (
logFeedID uint32 = iota
walletFeedID
)

type feedMessage struct {
FeedID uint32 `json:"feedID"`
Subject string `json:"subject"`
Payload interface{} `json:"payload"`
}

var feedChan = make(chan *feedMessage, 16)
var inited uint32

type initParams struct {
walletInitParams
LogLevel uint32 `json:"logLevel"`
}

func initUtility(raw json.RawMessage) (string, error) {

if !atomic.CompareAndSwapUint32(&inited, 0, 1) || theWallet() != nil {
return "", fmt.Errorf("already initialized")
}

init := new(initParams)
err := json.Unmarshal(raw, init)
if err != nil {
return "", err
}

w, err := newWallet(&init.walletInitParams)
if err != nil {
return "", fmt.Errorf("wallet init error: %v", err)
}

wlltMtx.Lock()
wllt = w
wlltMtx.Unlock()

// Just log to stdout for now. I thought sending logs
// through the feed would be a good idea, but now I'm thinking
// a separate log file and stdout when available.
// initializeLogging(btclog.Level(init.LogLevel))

go func() {
for {
select {
case msg := <-feedChan:
for feeder := range feeders {
msgB, err := json.Marshal(msg)
if err != nil {
log.Errorf("JSON Marshal error: %v", err)
continue
}
cStr := C.CString(string(msgB))
C.call_py_func(feeder, cStr)
FreeCharPtr(cStr)
}
case <-w.ctx.Done():
return
}
}
}()

return `true`, nil
}

var isShutdown uint32

func exitUtility(_ json.RawMessage) (string, error) {
w := theWallet()
if !atomic.CompareAndSwapUint32(&isShutdown, 0, 1) || w == nil {
return "", nil
}
w.Stop()
w.WaitForShutdown()
w.shutdown() // cancel the context
return "", nil
}

func main() {}

func init() {
useLogBackend(btclog.NewBackend(os.Stdout), btclog.LevelInfo)
}
89 changes: 89 additions & 0 deletions decred/decred/btc/btcwallet/libbtcwallet/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"time"

"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wallet/txauthor"
"github.com/btcsuite/btcwallet/wtxmgr"
)

type btcWallet interface {
MakeMultiSigScript(addrs []btcutil.Address, nRequired int) ([]byte, error)
ImportP2SHRedeemScript(script []byte) (*btcutil.AddressScriptHash, error)
// FundPsbt(packet *psbt.Packet, account uint32, feeSatPerKB btcutil.Amount) (int32, error)
// FinalizePsbt(packet *psbt.Packet) error
// SubmitRescan(job *RescanJob) <-chan error
// Rescan(addrs []btcutil.Address, unspent []wtxmgr.Credit) error
// ComputeInputScript(tx *wire.MsgTx, output *wire.TxOut, inputIndex int,
// sigHashes *txscript.TxSigHashes, hashType txscript.SigHashType, tweaker PrivKeyTweaker) (wire.TxWitness, []byte, error)
UnspentOutputs(policy wallet.OutputSelectionPolicy) ([]*wallet.TransactionOutput, error)
// FetchInputInfo(prevOut *wire.OutPoint) (*wire.MsgTx, *wire.TxOut, int64, error)
Start()
// SynchronizeRPC(chainClient chain.Interface)
// ChainClient() chain.Interface
Stop()
ShuttingDown() bool
WaitForShutdown()
SynchronizingToNetwork() bool
ChainSynced() bool
SetChainSynced(synced bool)
CreateSimpleTx(account uint32, outputs []*wire.TxOut, minconf int32, satPerKb btcutil.Amount, dryRun bool) (*txauthor.AuthoredTx, error)
Unlock(passphrase []byte, lock <-chan time.Time) error
Lock()
Locked() bool
ChangePrivatePassphrase(old, new []byte) error
ChangePublicPassphrase(old, new []byte) error
ChangePassphrases(publicOld, publicNew, privateOld, privateNew []byte) error
AccountAddresses(account uint32) (addrs []btcutil.Address, err error)
CalculateBalance(confirms int32) (btcutil.Amount, error)
CalculateAccountBalances(account uint32, confirms int32) (wallet.Balances, error)
CurrentAddress(account uint32, scope waddrmgr.KeyScope) (btcutil.Address, error)
PubKeyForAddress(a btcutil.Address) (*btcec.PublicKey, error)
LabelTransaction(hash chainhash.Hash, label string, overwrite bool) error
PrivKeyForAddress(a btcutil.Address) (*btcec.PrivateKey, error)
HaveAddress(a btcutil.Address) (bool, error)
AccountOfAddress(a btcutil.Address) (uint32, error)
AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error)
AccountNumber(scope waddrmgr.KeyScope, accountName string) (uint32, error)
AccountName(scope waddrmgr.KeyScope, accountNumber uint32) (string, error)
AccountProperties(scope waddrmgr.KeyScope, acct uint32) (*waddrmgr.AccountProperties, error)
RenameAccount(scope waddrmgr.KeyScope, account uint32, newName string) error
NextAccount(scope waddrmgr.KeyScope, name string) (uint32, error)
ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTransactionsResult, error)
ListTransactions(from, count int) ([]btcjson.ListTransactionsResult, error)
ListAddressTransactions(pkHashes map[string]struct{}) ([]btcjson.ListTransactionsResult, error)
ListAllTransactions() ([]btcjson.ListTransactionsResult, error)
// GetTransactions(startBlock, endBlock *BlockIdentifier, cancel <-chan struct{}) (*GetTransactionsResult, error)
Accounts(scope waddrmgr.KeyScope) (*wallet.AccountsResult, error)
AccountBalances(scope waddrmgr.KeyScope, requiredConfs int32) ([]wallet.AccountBalanceResult, error)
ListUnspent(minconf, maxconf int32, addresses map[string]struct{}) ([]*btcjson.ListUnspentResult, error)
DumpPrivKeys() ([]string, error)
DumpWIFPrivateKey(addr btcutil.Address) (string, error)
ImportPrivateKey(scope waddrmgr.KeyScope, wif *btcutil.WIF, bs *waddrmgr.BlockStamp, rescan bool) (string, error)
LockedOutpoint(op wire.OutPoint) bool
LockOutpoint(op wire.OutPoint)
UnlockOutpoint(op wire.OutPoint)
ResetLockedOutpoints()
LockedOutpoints() []btcjson.TransactionInput
LeaseOutput(id wtxmgr.LockID, op wire.OutPoint) (time.Time, error)
ReleaseOutput(id wtxmgr.LockID, op wire.OutPoint) error
SortedActivePaymentAddresses() ([]string, error)
NewAddress(account uint32, scope waddrmgr.KeyScope) (btcutil.Address, error)
NewChangeAddress(account uint32, scope waddrmgr.KeyScope) (btcutil.Address, error)
TotalReceivedForAccounts(scope waddrmgr.KeyScope, minConf int32) ([]wallet.AccountTotalReceivedResult, error)
TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcutil.Amount, error)
SendOutputs(outputs []*wire.TxOut, account uint32, minconf int32, satPerKb btcutil.Amount, label string) (*wire.MsgTx, error)
SignTransaction(tx *wire.MsgTx, hashType txscript.SigHashType, additionalPrevScripts map[wire.OutPoint][]byte,
additionalKeysByAddress map[string]*btcutil.WIF, p2shRedeemScriptsByAddress map[string][]byte) ([]wallet.SignatureError, error)
PublishTransaction(tx *wire.MsgTx, label string) error
// ChainParams() *chaincfg.Params
// Database() walletdb.DB
}
Loading