Skip to content

Commit

Permalink
implement basic wallet function and rpc for testing
Browse files Browse the repository at this point in the history
  • Loading branch information
whitefen committed Oct 29, 2018
1 parent b04b62b commit 961d4c0
Show file tree
Hide file tree
Showing 14 changed files with 1,542 additions and 58 deletions.
585 changes: 585 additions & 0 deletions logic/lwallet/lwallet.go

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions model/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,16 @@ func (tx *Tx) GetOuts() []*txout.TxOut {
return tx.outs
}

func (tx *Tx) InsertTxOut(pos int, txOut *txout.TxOut) {
if pos > len(tx.outs) {
tx.outs = append(tx.outs, txOut)
return
}
rear := append([]*txout.TxOut{}, tx.outs[pos:]...)
tx.outs = append(tx.outs[:pos], txOut)
tx.outs = append(tx.outs, rear...)
}

func NewTx(locktime uint32, version int32) *Tx {
tx := &Tx{lockTime: locktime, version: version}
tx.ins = make([]*txin.TxIn, 0)
Expand Down
45 changes: 45 additions & 0 deletions model/tx/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -935,3 +935,48 @@ func TestUpdateInScript(t *testing.T) {
err = tx.UpdateInScript(1, scriptSig)
assert.NotNil(t, err)
}

func TestInsertTxOut(t *testing.T) {
v := initVar()

scriptPubKey0 := script.NewEmptyScript()
scriptPubKey0.PushInt64(2)
for i := 0; i < 3; i++ {
scriptPubKey0.PushSingleData(v.pubKeys[i].ToBytes())
}
scriptPubKey0.PushInt64(3)
scriptPubKey0.PushOpCode(opcodes.OP_CHECKMULTISIG)

scriptPubKey1 := script.NewEmptyScript()
scriptPubKey1.PushOpCode(opcodes.OP_DUP)
scriptPubKey1.PushOpCode(opcodes.OP_HASH160)
scriptPubKey1.PushSingleData(btcutil.Hash160(v.pubKeys[0].ToBytes()))
scriptPubKey1.PushOpCode(opcodes.OP_EQUALVERIFY)
scriptPubKey1.PushOpCode(opcodes.OP_CHECKSIG)

pubKey := script.NewEmptyScript()
pubKey.PushSingleData(v.pubKeys[0].ToBytes())
pubKey.PushOpCode(opcodes.OP_CHECKSIG)
pubKeyHash160 := util.Hash160(pubKey.GetData())
scriptPubKey2 := script.NewEmptyScript()
scriptPubKey2.PushOpCode(opcodes.OP_HASH160)
scriptPubKey2.PushSingleData(pubKeyHash160)
scriptPubKey2.PushOpCode(opcodes.OP_EQUAL)

scriptPubKey3 := script.NewEmptyScript()
scriptPubKey3.PushSingleData([]byte{})

txn := NewTx(0, DefaultVersion)
txn.AddTxOut(txout.NewTxOut(0, scriptPubKey0))
txn.AddTxOut(txout.NewTxOut(0, scriptPubKey1))
assert.Equal(t, scriptPubKey0, txn.GetTxOut(0).GetScriptPubKey())
assert.Equal(t, scriptPubKey1, txn.GetTxOut(1).GetScriptPubKey())

txn.InsertTxOut(1, txout.NewTxOut(0, scriptPubKey2))
assert.Equal(t, scriptPubKey0, txn.GetTxOut(0).GetScriptPubKey())
assert.Equal(t, scriptPubKey2, txn.GetTxOut(1).GetScriptPubKey())
assert.Equal(t, scriptPubKey1, txn.GetTxOut(2).GetScriptPubKey())

txn.InsertTxOut(100, txout.NewTxOut(0, scriptPubKey3))
assert.Equal(t, scriptPubKey3, txn.GetTxOut(3).GetScriptPubKey())
}
39 changes: 39 additions & 0 deletions model/wallet/scriptstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package wallet

import (
"sync"

"github.com/copernet/copernicus/model/script"
"github.com/copernet/copernicus/util"
)

type ScriptStore struct {
lock *sync.RWMutex
scripts map[string]*script.Script
}

func NewScriptStore() *ScriptStore {
return &ScriptStore{
lock: new(sync.RWMutex),
scripts: make(map[string]*script.Script),
}
}

func (ss *ScriptStore) AddScript(s *script.Script) {
scriptHash := util.Hash160(s.Bytes())

ss.lock.Lock()
defer ss.lock.Unlock()

ss.scripts[string(scriptHash)] = s
}

func (ss *ScriptStore) GetScript(scriptHash []byte) *script.Script {
ss.lock.RLock()
defer ss.lock.RUnlock()

if s, ok := ss.scripts[string(scriptHash)]; ok {
return s
}
return nil
}
277 changes: 277 additions & 0 deletions model/wallet/wallet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
// Package wallet models the data for a wallet
// It is not a complete wallet and only provides basic testing capabilities for rpc currently
package wallet

import (
"github.com/copernet/copernicus/conf"
"github.com/copernet/copernicus/model/script"
"github.com/copernet/copernicus/model/utxo"
"github.com/copernet/copernicus/util/amount"
"sync"

"github.com/copernet/copernicus/crypto"
"github.com/copernet/copernicus/model/mempool"
"github.com/copernet/copernicus/model/outpoint"
"github.com/copernet/copernicus/model/tx"
"github.com/copernet/copernicus/util"
)

type AddressBook struct {
Account string
Purpose string
}

type Wallet struct {
enable bool
broadcastTx bool

keyStore *crypto.KeyStore
scriptStore *ScriptStore
reservedKeys []*crypto.PublicKey
addressBooks map[string]*AddressBook

txnLock *sync.RWMutex
walletTxns map[util.Hash]*WalletTx
lockedCoins map[outpoint.OutPoint]struct{}
payTxFee *util.FeeRate
}

var globalWallet *Wallet

/**
* If fee estimation does not have enough data to provide estimates, use this
* fee instead. Has no effect if not using fee estimation.
* Override with -fallbackfee
*/
var fallbackFee = util.NewFeeRate(20000)

func GetInstance() *Wallet {
if globalWallet == nil {
globalWallet = &Wallet{
enable: true,
broadcastTx: false,
keyStore: crypto.NewKeyStore(),
scriptStore: NewScriptStore(),
reservedKeys: make([]*crypto.PublicKey, 0),
addressBooks: make(map[string]*AddressBook),
txnLock: new(sync.RWMutex),
walletTxns: make(map[util.Hash]*WalletTx),
lockedCoins: make(map[outpoint.OutPoint]struct{}),
payTxFee: util.NewFeeRate(0),
}
}
return globalWallet
}

func (w *Wallet) IsEnable() bool {
return w.enable
}

func (w *Wallet) SetEnable(enable bool) {
w.enable = enable
}

func (w *Wallet) GenerateNewKey() *crypto.PublicKey {
randBytes := util.GetRandHash()[:]
privateKey := crypto.NewPrivateKeyFromBytes(randBytes, true)
w.keyStore.AddKey(privateKey)
return privateKey.PubKey()
}

func (w *Wallet) GetReservedKey() *crypto.PublicKey {
// wallet function is only for testing. The keypool is not supported yet.
// generate new key each time
reservedKey := w.GenerateNewKey()
w.reservedKeys = append(w.reservedKeys, reservedKey)
return reservedKey

}

func (w *Wallet) GetAddressBook(keyHash []byte) *AddressBook {
return w.addressBooks[string(keyHash)]
}

func (w *Wallet) SetAddressBook(keyHash []byte, account string, purpose string) {
w.addressBooks[string(keyHash)] = &AddressBook{
Account: account,
Purpose: purpose,
}
}

func (w *Wallet) GetKeyPairs(pubKeyHashList [][]byte) []*crypto.KeyPair {
return w.keyStore.GetKeyPairs(pubKeyHashList)
}

func (w *Wallet) GetWalletTxns() []*WalletTx {
walletTxns := make([]*WalletTx, 0, len(w.walletTxns))

w.txnLock.RLock()
defer w.txnLock.RUnlock()

for _, walletTx := range w.walletTxns {
walletTxns = append(walletTxns, walletTx)
}
return walletTxns
}

func (w *Wallet) IsTrusted(walletTx *WalletTx) bool {
// Quick answer in most cases
if !walletTx.CheckFinalForForCurrentBlock() {
return false
}

depth := walletTx.GetDepthInMainChain()
if depth >= 1 {
return true
}

// Don't trust unconfirmed transactions from us unless they are in the
// mempool.
if !mempool.GetInstance().IsTransactionInPool(walletTx.Tx) {
return false
}

w.txnLock.RLock()
defer w.txnLock.RUnlock()

// Trusted if all inputs are from us and are in the mempool:
for _, txIn := range walletTx.Tx.GetIns() {
// Transactions not sent by us: not trusted
prevTxn, ok := w.walletTxns[txIn.PreviousOutPoint.Hash]
if !ok {
return false
}
prevOut := prevTxn.Tx.GetTxOut(int(txIn.PreviousOutPoint.Index))
if !IsUnlockable(prevOut.GetScriptPubKey()) {
return false
}
}

return true
}

func (w *Wallet) GetScript(scriptHash []byte) *script.Script {
return w.scriptStore.GetScript(scriptHash)
}

func (w *Wallet) GetBalance() amount.Amount {
balance := amount.Amount(0)

w.txnLock.RLock()
defer w.txnLock.RUnlock()

for _, walletTx := range w.walletTxns {
if w.IsTrusted(walletTx) {
balance += walletTx.GetAvailableCredit(true)
}
}
return balance
}

func (w *Wallet) GetBroadcastTx() bool {
return w.broadcastTx
}

func (w *Wallet) SetBroadcastTx(broadcastTx bool) {
w.broadcastTx = broadcastTx
}

func (w *Wallet) SetFeeRate(feePaid int64, byteSize int64) {
w.payTxFee = util.NewFeeRateWithSize(feePaid, byteSize)
}

func (w *Wallet) AddToWallet(txn *tx.Tx, extInfo map[string]string) {
if extInfo == nil {
extInfo = make(map[string]string)
}
walletTxn := NewWalletTx(txn, extInfo, true, "")
w.txnLock.Lock()
defer w.txnLock.Unlock()
w.walletTxns[txn.GetHash()] = walletTxn
}

func (w *Wallet) GetMinimumFee(byteSize int) int64 {
feeNeeded := w.payTxFee.GetFee(byteSize)
// User didn't set tx fee
if feeNeeded == 0 {
minFeeRate := mempool.GetInstance().GetMinFeeRate()
feeNeeded = minFeeRate.GetFee(byteSize)

// ... unless we don't have enough mempool data for estimatefee, then
// use fallbackFee.
if feeNeeded == 0 {
feeNeeded = fallbackFee.GetFee(byteSize)
}
}

// Prevent user from paying a fee below minRelayTxFee or minTxFee.
cfgMinFeeRate := util.NewFeeRate(conf.Cfg.Mempool.MinFeeRate)
feeNeeded = util.MaxI(feeNeeded, cfgMinFeeRate.GetFee(byteSize))

// But always obey the maximum.
feeNeeded = util.MinI(feeNeeded, util.MaxFee)

return feeNeeded
}

func (w *Wallet) GetUnspentCoin(outPoint *outpoint.OutPoint) *utxo.Coin {
w.txnLock.RLock()
defer w.txnLock.RUnlock()
if wtx, ok := w.walletTxns[outPoint.Hash]; ok {
return wtx.GetUnspentCoin(int(outPoint.Index))
}
return nil
}

func (w *Wallet) MarkSpent(outPoint *outpoint.OutPoint) {
w.txnLock.RLock()
defer w.txnLock.RUnlock()
if wtx, ok := w.walletTxns[outPoint.Hash]; ok {
wtx.MarkSpent(int(outPoint.Index))
}
}

func IsUnlockable(scriptPubKey *script.Script) bool {
if globalWallet == nil || scriptPubKey == nil {
return false
}

pubKeyType, pubKeys, isStandard := scriptPubKey.IsStandardScriptPubKey()
if !isStandard || pubKeyType == script.ScriptNonStandard || pubKeyType == script.ScriptNullData {
return false
}

if pubKeyType == script.ScriptHash {
redeemScript := globalWallet.scriptStore.GetScript(pubKeys[0])
if redeemScript == nil {
return false
}
pubKeyType, pubKeys, isStandard = redeemScript.IsStandardScriptPubKey()
if !isStandard || pubKeyType == script.ScriptNonStandard || pubKeyType == script.ScriptNullData {
return false
}
}

if pubKeyType == script.ScriptPubkey {
pubKeyHash := util.Hash160(pubKeys[0])
return globalWallet.keyStore.GetKeyPair(pubKeyHash) != nil

} else if pubKeyType == script.ScriptPubkeyHash {
return globalWallet.keyStore.GetKeyPair(pubKeys[0]) != nil

} else if pubKeyType == script.ScriptMultiSig {
// Only consider transactions "mine" if we own ALL the keys
// involved. Multi-signature transactions that are partially owned
// (somebody else has a key that can spend them) enable
// spend-out-from-under-you attacks, especially in shared-wallet
// situations.
for _, pubKey := range pubKeys[1:] {
pubKeyHash := util.Hash160(pubKey)
if globalWallet.keyStore.GetKeyPair(pubKeyHash) == nil {
return false
}
}
return true
}
return false
}
Loading

0 comments on commit 961d4c0

Please sign in to comment.