Skip to content

Commit

Permalink
initial cgo code
Browse files Browse the repository at this point in the history
  • Loading branch information
itswisdomagain committed Dec 21, 2023
1 parent 36de1eb commit 0a6d9b4
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
26 changes: 26 additions & 0 deletions cgo/addresses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import "C"
import "decred.org/dcrwallet/v3/wallet/udb"

//export currentReceiveAddress
func currentReceiveAddress(walletName *C.char) *C.char {
w, ok := loadedWallet(walletName)
if !ok {
return nil
}

// Don't return an address if not synced!
if !w.IsSynced() {
w.log.Trace("currentReceiveAddress requested on an unsynced wallet")
return nil
}

addr, err := w.CurrentAddress(udb.DefaultAccountNum)
if err != nil {
w.log.Errorf("w.CurrentAddress error: %v", err)
return nil
}

return cString(addr.String())
}
82 changes: 82 additions & 0 deletions cgo/cgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// A pacakge that exports Decred wallet functionalities as go code that can be
// compiled into a c-shared libary. Must be a main package, with an empty main
// function. And functions to be exported must have an "//export {fnName}"
// comment.
//
// Build cmd: go build -buildmode=c-archive -o {path_to_generated_library} ./cgo
// E.g. go build -buildmode=c-archive -o ./build/libdcrwallet.a ./cgo.

package main

import "C"
import (
"context"
"sync"

"github.com/decred/slog"
"github.com/itswisdomagain/libwallet/asset/dcr"
"github.com/itswisdomagain/libwallet/assetlog"
)

var (
ctx context.Context
cancelCtx context.CancelFunc
wg sync.WaitGroup

logBackend *parentLogger
log slog.Logger

walletsMtx sync.RWMutex
wallets map[string]*wallet
)

//export initialize
func initialize(cLogDir *C.char) *C.char {
walletsMtx.Lock()
defer walletsMtx.Unlock()
if wallets != nil {
return cString("duplicate initialization")
}

logDir := goString(cLogDir)
logSpinner, err := assetlog.NewRotator(logDir, "dcrwallet.log")
if err != nil {
return cStringF("error initializing log rotator: %v", err)
}

logBackend = newParentLogger(logSpinner)
err = dcr.InitGlobalLogging(logDir, logBackend)
if err != nil {
return cStringF("error initializing logger for external pkgs: %v", err)
}

log = logBackend.SubLogger("[APP]")
log.SetLevel(slog.LevelTrace)

ctx, cancelCtx = context.WithCancel(context.Background())
wallets = make(map[string]*wallet)

log.Info("libwallet cgo initialized")
return nil
}

//export shutdown
func shutdown() {
walletsMtx.Lock()
for _, wallet := range wallets {
if err := wallet.CloseWallet(); err != nil {
wallet.log.Errorf("close wallet error: %v", err)
}
}
wallets = nil // cannot be reused unless initialize is called again.
walletsMtx.Unlock()

// Stop all remaining background processes and wait for them to stop.
cancelCtx()
wg.Wait()

// Close the logger backend as the last step.
logBackend.Close()
}

func main() {}
26 changes: 26 additions & 0 deletions cgo/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"github.com/decred/slog"
"github.com/jrick/logrotate/rotator"
)

type parentLogger struct {
*slog.Backend
rotator *rotator.Rotator
}

func newParentLogger(rotator *rotator.Rotator) *parentLogger {
return &parentLogger{
Backend: slog.NewBackend(rotator),
rotator: rotator,
}
}

func (pl *parentLogger) SubLogger(name string) slog.Logger {
return pl.Logger(name)
}

func (pl *parentLogger) Close() error {
return pl.rotator.Close()
}
34 changes: 34 additions & 0 deletions cgo/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import "C"
import (
"fmt"
)

func loadedWallet(cName *C.char) (*wallet, bool) {
walletsMtx.Lock()
defer walletsMtx.Unlock()

name := goString(cName)
w, ok := wallets[name]
if !ok {
log.Debugf("attempted to use an unloaded wallet %q", name)
}
return w, ok
}

func cString(str string) *C.char {
return C.CString(str)
}

func cStringF(format string, a ...any) *C.char {
return C.CString(fmt.Sprintf(format, a...))
}

func cError(err error) *C.char {
return C.CString(err.Error())
}

func goString(cstr *C.char) string {
return C.GoString(cstr)
}
147 changes: 147 additions & 0 deletions cgo/walletloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package main

import "C"
import (
"encoding/json"

"github.com/decred/slog"
"github.com/itswisdomagain/libwallet/asset"
"github.com/itswisdomagain/libwallet/asset/dcr"
)

const emptyJsonObject = "{}"

type wallet struct {
*dcr.Wallet
log slog.Logger
}

//export createWallet
func createWallet(cName, cDataDir, cNet, cPass *C.char) *C.char {
walletsMtx.Lock()
defer walletsMtx.Unlock()
if wallets == nil {
return cString("libwallet is not initialized")
}

name := goString(cName)
if _, exists := wallets[name]; exists {
return cStringF("wallet already exists with name: %q", name)
}

network, err := asset.NetFromString(goString(cNet))
if err != nil {
return cError(err)
}

logger := logBackend.Logger("[" + name + "]")
logger.SetLevel(slog.LevelTrace)
params := asset.CreateWalletParams{
OpenWalletParams: asset.OpenWalletParams{
Net: network,
DataDir: goString(cDataDir),
DbDriver: "bdb", // use badgerdb for mobile!
Logger: logger,
},
Pass: []byte(goString(cPass)),
}
w, err := dcr.CreateWallet(ctx, params, nil)
if err != nil {
return cError(err)
}

wallets[name] = &wallet{
Wallet: w,
log: logger,
}
return nil
}

//export loadWallet
func loadWallet(cName, cDataDir, cNet *C.char) *C.char {
walletsMtx.Lock()
defer walletsMtx.Unlock()
if wallets == nil {
return cString("libwallet is not initialized")
}

name := goString(cName)
if _, exists := wallets[name]; exists {
return nil // not an error, already loaded
}

network, err := asset.NetFromString(goString(cNet))
if err != nil {
return cError(err)
}

logger := logBackend.Logger("[" + name + "]")
logger.SetLevel(slog.LevelTrace)
params := asset.OpenWalletParams{
Net: network,
DataDir: goString(cDataDir),
DbDriver: "bdb", // use badgerdb for mobile!
Logger: logger,
}
w, err := dcr.LoadWallet(ctx, params)
if err != nil {
return cError(err)
}

if err = w.OpenWallet(ctx); err != nil {
return cError(err)
}

wallets[name] = &wallet{
Wallet: w,
log: logger,
}
return nil
}

//export walletSeed
func walletSeed(name, pass *C.char) *C.char {
w, ok := loadedWallet(name)
if !ok {
return nil
}

seed, err := w.DecryptSeed([]byte(goString(pass)))
if err != nil {
w.log.Errorf("w.RevealSeed error: %v", err)
return nil
}

return cString(seed)
}

//export walletBalance
func walletBalance(name *C.char) *C.char {
w, ok := loadedWallet(name)
if !ok {
return cString(emptyJsonObject)
}

balMap := map[string]float64{
"confirmed": 0,
"unconfirmed": 0,
}

bals, err := w.AccountBalances(ctx, 0)
if err != nil {
w.log.Errorf("w.AccountBalances error: %v", err)
} else {
for _, bal := range bals {
balMap["confirmed"] += bal.Spendable.ToCoin()
balMap["unconfirmed"] += bal.Total.ToCoin() - bal.Spendable.ToCoin()
}
}

balJson, err := json.Marshal(balMap)
if err != nil {
w.log.Errorf("marshal balMap error: %v", err)
return cString(emptyJsonObject)
}

return cString(string(balJson))
}

0 comments on commit 0a6d9b4

Please sign in to comment.