Skip to content

Commit

Permalink
permenent storage for address mapping (#16)
Browse files Browse the repository at this point in the history
* gkv storage for btc to eth address mapping

* fixes

* fixes test

---------

Co-authored-by: dpiatkivskyi <dmytro.piatkikvskyi@gmailcom>
  • Loading branch information
dmytropiatkivskyi and dpiatkivskyi authored Jun 18, 2024
1 parent 83ab76c commit 9d2bb6c
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 22 deletions.
6 changes: 6 additions & 0 deletions run/btcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,21 @@ func InitWallet(signer frost.Signer, pk1, pk2 *btcec.PublicKey, bitcoindConfig *
// Load the wallet database. It must have been created already
// or this will return an appropriate error.
w, err = loader.OpenExistingWallet([]byte(cfg.WalletPass), true)
if err != nil {
log.Error(err)
return nil, err
}

w.FrostSigner = signer
w.Pk1 = pk1
w.Pk2 = pk2

storage, err := wallet.NewAddressMapStorage(cfg.AppDataDir.Value + "/" + wallet.DefaultStorageFileName)
if err != nil {
log.Error(err)
return nil, err
}
w.AddressMapStorage = storage
}

// Add interrupt handlers to shutdown the various process components
Expand Down
20 changes: 14 additions & 6 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,10 @@ func (w *Wallet) txToOutputsWithRedemptionId(outputs []*wire.TxOut,
}
if !watchOnly || containsTaprootInput(tx) {

linearCombinations := getTaprootPubKeys(tx, w)
linearCombinations, err := getTaprootPubKeys(tx, w)
if err != nil {
return err
}
err = tx.AddAllInputScripts(w.FrostSigner, linearCombinations,
secretSource{w.Manager, addrmgrNs},
)
Expand Down Expand Up @@ -350,7 +353,7 @@ func containsTaprootInput(tx *txauthor.AuthoredTx) bool {
return false
}

func getTaprootPubKeys(tx *txauthor.AuthoredTx, w *Wallet) map[string]*crypto.LinearCombination {
func getTaprootPubKeys(tx *txauthor.AuthoredTx, w *Wallet) (map[string]*crypto.LinearCombination, error) {
linearCombinations := make(map[string]*crypto.LinearCombination)
for i := range tx.Tx.TxIn {
pkScript := tx.PrevScripts[i]
Expand All @@ -362,14 +365,19 @@ func getTaprootPubKeys(tx *txauthor.AuthoredTx, w *Wallet) map[string]*crypto.Li
}

for _, addr := range addrs {
lc, ok := w.btcAddrToLc[addr.String()]
if ok {
linearCombinations[addr.String()] = lc
ethAddr, err := w.AddressMapStorage.GetEthAddress(addr.String())
if err != nil {
return nil, err
}
lc, err := w.lcFromEthAddr(ethAddr)
if err != nil {
return nil, err
}
linearCombinations[addr.String()] = lc
}
}
}
return linearCombinations
return linearCombinations, nil
}

func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx,
Expand Down
4 changes: 4 additions & 0 deletions wallet/frost_signing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ func TestFrostSigning(t *testing.T) {
w.Pk1 = pk1
w.Pk2 = pk2

storage, err := NewAddressMapStorage("")
require.NoError(t, err)
w.AddressMapStorage = storage

err = w.Unlock([]byte("world"), time.After(10*time.Minute))
require.NoError(t, err)

Expand Down
25 changes: 15 additions & 10 deletions wallet/import_btc_addr.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ func (w *Wallet) ImportBtcAddressWithEthAddr(btcAddr, ethAddr string) (*btcec.Pu
if importedAddress != nil {
address := importedAddress.Address().EncodeAddress()
if btcAddr != "" && address != btcAddr {
return nil, fmt.Errorf("address mismatch: %s != %s", importedAddress, btcAddr)
return nil, fmt.Errorf("address mismatch: %s != %s",
importedAddress.Address().EncodeAddress(), btcAddr)
}
err := w.AddressMapStorage.SetEthAddress(address, ethAddr)
if err != nil {
return nil, err
}
w.btcAddrToLc[address] = lc
w.btcAddrToEthAddr[address] = ethAddr
}

return pubKey, nil
Expand All @@ -52,22 +55,24 @@ func (w *Wallet) lcFromEthAddr(ethAddrStr string) (*crypto.LinearCombination, er
},
}

pk1, pk2, err := w.GetSignerPublicKeys()
if err != nil {
return nil, err
if w.Pk1 == nil {
return nil, fmt.Errorf("missing pk1")
}
if w.Pk2 == nil {
return nil, fmt.Errorf("missing pk2")
}

b1, _ := arguments.Pack(
pk1.X(),
pk1.Y(),
w.Pk1.X(),
w.Pk1.Y(),
ethAddr,
)
h1 := crypto.Sha256(b1)
c1FromAddr, _ := crypto.PrivKeyFromBytes(h1[:])

b2, _ := arguments.Pack(
pk2.X(),
pk2.Y(),
w.Pk2.X(),
w.Pk2.Y(),
ethAddr,
)
h2 := crypto.Sha256(b2)
Expand Down
71 changes: 71 additions & 0 deletions wallet/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package wallet

import (
"errors"
"fmt"
"github.com/steveyen/gkvlite"
"os"
"sync"
)

const DefaultStorageFileName = "addressMap.gkv"
const collectionName = "ethAddress"

type AddressMapStorage struct {
store *gkvlite.Store
lock *sync.RWMutex
}

// NewAddressMapStorage creates a new storage instance using gkvlite with file path param
func NewAddressMapStorage(storageFilePath string) (*AddressMapStorage, error) {
if storageFilePath == "" {
storageFilePath = DefaultStorageFileName
}

// Open the storage file
file, err := os.OpenFile(storageFilePath, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return nil, fmt.Errorf("cannot open storage file: %w", err)
}

store, err := gkvlite.NewStore(file)
if err != nil {
_ = file.Close()
return nil, fmt.Errorf("cannot create storage: %w", err)
}

store.SetCollection(collectionName, nil)

return &AddressMapStorage{
store: store,
lock: new(sync.RWMutex),
}, nil
}

func (s *AddressMapStorage) GetEthAddress(btcAddress string) (string, error) {
s.lock.RLock()
defer s.lock.RUnlock()
c := s.store.GetCollection(collectionName)
if c == nil {
return "", errors.New("no such collection: " + collectionName)
}
ethAddress, err := c.Get([]byte(btcAddress))
if err != nil {
return "", err
}
return string(ethAddress), err
}

func (s *AddressMapStorage) SetEthAddress(btcAddress, ethAddress string) error {
s.lock.Lock()
defer s.lock.Unlock()
c := s.store.GetCollection(collectionName)
if c == nil {
return errors.New("no such collection: " + collectionName)
}
if err := c.Set([]byte(btcAddress), []byte(ethAddress)); err != nil {
return fmt.Errorf("cannot set: %w", err)
}

return s.store.Flush()
}
30 changes: 30 additions & 0 deletions wallet/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package wallet

import (
"github.com/stretchr/testify/require"
"os"
"testing"
)

func TestStorage(t *testing.T) {
storage, err := NewAddressMapStorage("")
require.NoError(t, err)

err = storage.SetEthAddress("btcAddress", "ethAddress")
require.NoError(t, err)

storage.store.Close()

storage2, err := NewAddressMapStorage("")
require.NoError(t, err)

ethAddress, err := storage2.GetEthAddress("btcAddress")
require.NoError(t, err)

require.Equal(t, "ethAddress", ethAddress)

t.Cleanup(func() {
storage2.store.Close()
_ = os.Remove(DefaultStorageFileName)
})
}
8 changes: 2 additions & 6 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/stroomnetwork/frost/crypto"
"sort"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -132,9 +131,8 @@ type Wallet struct {
TxStore *wtxmgr.Store
FrostSigner frost.Signer

btcAddrToEthAddr map[string]string
btcAddrToLc map[string]*crypto.LinearCombination
Pk1, Pk2 *btcec.PublicKey
AddressMapStorage *AddressMapStorage
Pk1, Pk2 *btcec.PublicKey

chainClient chain.Interface
chainClientLock sync.Mutex
Expand Down Expand Up @@ -4078,8 +4076,6 @@ func OpenWithRetry(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks,
db: db,
Manager: addrMgr,
TxStore: txMgr,
btcAddrToLc: make(map[string]*crypto.LinearCombination),
btcAddrToEthAddr: make(map[string]string),
lockedOutpoints: map[wire.OutPoint]struct{}{},
recoveryWindow: recoveryWindow,
rescanAddJob: make(chan *RescanJob),
Expand Down

0 comments on commit 9d2bb6c

Please sign in to comment.