From a19e55e2f5a7e6812cf1b3b5c0071fa5c157356e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Fri, 5 Oct 2018 00:13:42 +0300 Subject: [PATCH 01/25] rpc\legacyrpc\methods: Add transferTransaction handler --- rpc/legacyrpc/methods.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 9251a91670..9b5c6cbd32 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -101,6 +101,7 @@ var rpcHandlers = map[string]struct { "lockunspent": {handler: lockUnspent}, "sendfrom": {handlerWithChain: sendFrom}, "sendmany": {handler: sendMany}, + "transferTransaction": {handler: transferTransaction}, "sendtoaddress": {handler: sendToAddress}, "settxfee": {handler: setTxFee}, "signmessage": {handler: signMessage}, @@ -1492,6 +1493,41 @@ func sendMany(icmd interface{}, w *wallet.Wallet) (interface{}, error) { return sendPairs(w, pairs, account, minConf, txrules.DefaultRelayFeePerKb) } +func transferTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error) { + cmd := icmd.(*btcjson.TransferTransactionCmd) + return transferToAddress(w, + cmd.Address, cmd.TxId, + waddrmgr.DefaultAccountNum, 1, txrules.DefaultRelayFeePerKb) +} + +func transferToAddress(w *wallet.Wallet, addrStr string, txId string, + account uint32, minconf int32, feeSatPerKb btcutil.Amount) (string, error) { + + txHash, err := w.TransferTx(addrStr, txId, account, minconf, feeSatPerKb) + if err != nil { + // TODO : check for tx is not found error + // TODO : check for tx is not mature error + // TODO : check for fee not enough + + if waddrmgr.IsError(err, waddrmgr.ErrLocked) { + return "", &ErrWalletUnlockNeeded + } + switch err.(type) { + case btcjson.RPCError: + return "", err + } + + return "", &btcjson.RPCError{ + Code: btcjson.ErrRPCInternal.Code, + Message: err.Error(), + } + } + + txHashStr := txHash.String() + log.Infof("Successfully transferred transaction %v", txHashStr) + return txHashStr, nil +} + // sendToAddress handles a sendtoaddress RPC request by creating a new // transaction spending unspent transaction outputs for a wallet to another // payment address. Leftover inputs not sent to the payment address or a fee From 0697c2d20ecadea38ac09fd224bec9962c177d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Fri, 5 Oct 2018 00:16:04 +0300 Subject: [PATCH 02/25] wallet\wallet: Add TransferTx method for transfertransaction RPC command --- wallet/createtx.go | 9 ++++++ wallet/wallet.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/wallet/createtx.go b/wallet/createtx.go index dc831f865d..688f0b9ee3 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -96,6 +96,15 @@ func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) { return msa.Script() } +// TODO : Write summary +func (w *Wallet) txTransferToOutputs(address string, txId string, account uint32, + minconf int32, feeSatPerKB btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { + //TODO : Implement this + fmt.Printf("txTransferToOutputs") + + return nil, nil +} + // txToOutputs creates a signed transaction which includes each output from // outputs. Previous outputs to reedeem are chosen from the passed account's // UTXO set and minconf policy. An additional output may be added to return diff --git a/wallet/wallet.go b/wallet/wallet.go index b202d69021..48b8e3ec41 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -97,6 +97,9 @@ type Wallet struct { // Channel for transaction creation requests. createTxRequests chan createTxRequest + // Channel for transaction transfer requests + createTxTransferRequests chan createTxTransferRequest + // Channels for the manager locker. unlockRequests chan unlockRequest lockRequests chan struct{} @@ -140,6 +143,7 @@ func (w *Wallet) Start() { w.wg.Add(2) go w.txCreator() + go w.txTransferCreator() go w.walletLocker() } @@ -1120,6 +1124,75 @@ out: w.wg.Done() } +// TODO : Write summary +type ( + createTxTransferRequest struct { + account uint32 + address string + txId string + minconf int32 + feeSatPerKB btcutil.Amount + resp chan createTxTransferResponse + } + createTxTransferResponse struct { + tx *txauthor.AuthoredTx + err error + } +) + +// TODO : Write summary +func (w *Wallet) txTransferCreator() { + quit := w.quitChan() +out: + for { + select { + case txr := <-w.createTxTransferRequests: + heldUnlock, err := w.holdUnlock() + if err != nil { + txr.resp <- createTxTransferResponse{nil, err} + continue + } + tx, err := w.txTransferToOutputs(txr.address, txr.txId, txr.account, + txr.minconf, txr.feeSatPerKB) + heldUnlock.release() + txr.resp <- createTxTransferResponse{tx, err} + case <-quit: + break out + } + } + w.wg.Done() +} + +// TODO : Write summary +func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txId string, minconf int32, feeSatPerKb btcutil.Amount) (*txauthor.AuthoredTx, error) { + req := createTxTransferRequest{ + account: account, + address: address, + txId: txId, + minconf: minconf, + feeSatPerKB: feeSatPerKb, + resp: make(chan createTxTransferResponse), + } + w.createTxTransferRequests <- req + resp := <-req.resp + return resp.tx, resp.err +} + +// TODO : Write summary +func (w *Wallet) TransferTx(address string, txId string, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (*chainhash.Hash, error) { + _, err := w.CreateSimpleTxTransfer(account, address, txId, minconf, feeSatPerKb) + if err != nil { + return nil, err + } + // TODO : Use below code block inside CreateSimpleTxTransfer + /* for _, output := range outputs { + if err := txrules.CheckOutput(output, satPerKb); err != nil { + return nil, err + } + } */ + return nil, nil +} + // CreateSimpleTx creates a new signed transaction spending unspent P2PKH // outputs with at laest minconf confirmations spending to any number of // address/amount pairs. Change and an appropriate transaction fee are From 4edeca76cb67b2f6590a9a94aa707a299511aff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sat, 6 Oct 2018 10:05:15 +0300 Subject: [PATCH 03/25] wallet, rpc\legacyrpc\methods: Fix multiple bugs rpc\legacyrpc\methods: Fix casing error in 'transfertransaction' rpchelp: Add RPC help for transfertransaction wallet: Add missing make(chan) for 'createTxTransferRequests' --- internal/rpchelp/methods.go | 1 + rpc/legacyrpc/methods.go | 13 ++++++----- rpc/legacyrpc/rpcserverhelp.go | 1 + wallet/wallet.go | 41 +++++++++++++++++----------------- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/internal/rpchelp/methods.go b/internal/rpchelp/methods.go index cdc4fad4a8..b49cdbab75 100644 --- a/internal/rpchelp/methods.go +++ b/internal/rpchelp/methods.go @@ -52,6 +52,7 @@ var Methods = []struct { {"sendfrom", returnsString}, {"sendmany", returnsString}, {"sendtoaddress", returnsString}, + {"transfertransaction", returnsString}, {"settxfee", returnsBool}, {"signmessage", returnsString}, {"signrawtransaction", []interface{}{(*btcjson.SignRawTransactionResult)(nil)}}, diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 9b5c6cbd32..96a1cce821 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -101,7 +101,7 @@ var rpcHandlers = map[string]struct { "lockunspent": {handler: lockUnspent}, "sendfrom": {handlerWithChain: sendFrom}, "sendmany": {handler: sendMany}, - "transferTransaction": {handler: transferTransaction}, + "transfertransaction": {handler: transferTransaction}, "sendtoaddress": {handler: sendToAddress}, "settxfee": {handler: setTxFee}, "signmessage": {handler: signMessage}, @@ -1495,15 +1495,18 @@ func sendMany(icmd interface{}, w *wallet.Wallet) (interface{}, error) { func transferTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error) { cmd := icmd.(*btcjson.TransferTransactionCmd) - return transferToAddress(w, - cmd.Address, cmd.TxId, + + address := cmd.Address + txId := cmd.TxId + + return transferToAddress(w, address, txId, waddrmgr.DefaultAccountNum, 1, txrules.DefaultRelayFeePerKb) } func transferToAddress(w *wallet.Wallet, addrStr string, txId string, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (string, error) { - txHash, err := w.TransferTx(addrStr, txId, account, minconf, feeSatPerKb) + _, err := w.TransferTx(addrStr, txId, account, minconf, feeSatPerKb) //txHash if err != nil { // TODO : check for tx is not found error // TODO : check for tx is not mature error @@ -1523,7 +1526,7 @@ func transferToAddress(w *wallet.Wallet, addrStr string, txId string, } } - txHashStr := txHash.String() + txHashStr := "0000-0000-0000-0001" //txHash.String() log.Infof("Successfully transferred transaction %v", txHashStr) return txHashStr, nil } diff --git a/rpc/legacyrpc/rpcserverhelp.go b/rpc/legacyrpc/rpcserverhelp.go index f78fb6731d..aaaa304ba2 100644 --- a/rpc/legacyrpc/rpcserverhelp.go +++ b/rpc/legacyrpc/rpcserverhelp.go @@ -32,6 +32,7 @@ func helpDescsEnUS() map[string]string { "lockunspent": "lockunspent unlock [{\"txid\":\"value\",\"vout\":n},...]\n\nLocks or unlocks an unspent output.\nLocked outputs are not chosen for transaction inputs of authored transactions and are not included in 'listunspent' results.\nLocked outputs are volatile and are not saved across wallet restarts.\nIf unlock is true and no transaction outputs are specified, all locked outputs are marked unlocked.\n\nArguments:\n1. unlock (boolean, required) True to unlock outputs, false to lock\n2. transactions (array of object, required) Transaction outputs to lock or unlock\n[{\n \"txid\": \"value\", (string) The transaction hash of the referenced output\n \"vout\": n, (numeric) The output index of the referenced output\n},...]\n\nResult:\ntrue|false (boolean) The boolean 'true'\n", "sendfrom": "sendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\n\nDEPRECATED -- Authors, signs, and sends a transaction that outputs some amount to a payment address.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) Account to pick unspent outputs from\n2. toaddress (string, required) Address to pay\n3. amount (numeric, required) Amount to send to the payment address valued in bitcoin\n4. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n5. comment (string, optional) Unused\n6. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", "sendmany": "sendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\n\nAuthors, signs, and sends a transaction that outputs to many payment addresses.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. fromaccount (string, required) DEPRECATED -- Account to pick unspent outputs from\n2. amounts (object, required) Pairs of payment addresses and the output amount to pay each\n{\n \"Address to pay\": Amount to send to the payment address valued in bitcoin, (object) JSON object using payment addresses as keys and output amounts valued in bitcoin to send to each address\n ...\n}\n3. minconf (numeric, optional, default=1) Minimum number of block confirmations required before a transaction output is eligible to be spent\n4. comment (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", + "transfertransaction": "transfertransaction", "sendtoaddress": "sendtoaddress \"address\" amount (\"comment\" \"commentto\")\n\nAuthors, signs, and sends a transaction that outputs some amount to a payment address.\nUnlike sendfrom, outputs are always chosen from the default account.\nA change output is automatically included to send extra output value back to the original account.\n\nArguments:\n1. address (string, required) Address to pay\n2. amount (numeric, required) Amount to send to the payment address valued in bitcoin\n3. comment (string, optional) Unused\n4. commentto (string, optional) Unused\n\nResult:\n\"value\" (string) The transaction hash of the sent transaction\n", "settxfee": "settxfee amount\n\nModify the increment used each time more fee is required for an authored transaction.\n\nArguments:\n1. amount (numeric, required) The new fee increment valued in bitcoin\n\nResult:\ntrue|false (boolean) The boolean 'true'\n", "signmessage": "signmessage \"address\" \"message\"\n\nSigns a message using the private key of a payment address.\n\nArguments:\n1. address (string, required) Payment address of private key used to sign the message with\n2. message (string, required) Message to sign\n\nResult:\n\"value\" (string) The signed message encoded as a base64 string\n", diff --git a/wallet/wallet.go b/wallet/wallet.go index 48b8e3ec41..bc6c081576 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -3506,26 +3506,27 @@ func Open(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks, log.Infof("Opened wallet") // TODO: log balance? last sync height? w := &Wallet{ - publicPassphrase: pubPass, - db: db, - Manager: addrMgr, - TxStore: txMgr, - lockedOutpoints: map[wire.OutPoint]struct{}{}, - recoveryWindow: recoveryWindow, - rescanAddJob: make(chan *RescanJob), - rescanBatch: make(chan *rescanBatch), - rescanNotifications: make(chan interface{}), - rescanProgress: make(chan *RescanProgressMsg), - rescanFinished: make(chan *RescanFinishedMsg), - createTxRequests: make(chan createTxRequest), - unlockRequests: make(chan unlockRequest), - lockRequests: make(chan struct{}), - holdUnlockRequests: make(chan chan heldUnlock), - lockState: make(chan bool), - changePassphrase: make(chan changePassphraseRequest), - changePassphrases: make(chan changePassphrasesRequest), - chainParams: params, - quit: make(chan struct{}), + publicPassphrase: pubPass, + db: db, + Manager: addrMgr, + TxStore: txMgr, + lockedOutpoints: map[wire.OutPoint]struct{}{}, + recoveryWindow: recoveryWindow, + rescanAddJob: make(chan *RescanJob), + rescanBatch: make(chan *rescanBatch), + rescanNotifications: make(chan interface{}), + rescanProgress: make(chan *RescanProgressMsg), + rescanFinished: make(chan *RescanFinishedMsg), + createTxRequests: make(chan createTxRequest), + createTxTransferRequests: make(chan createTxTransferRequest), + unlockRequests: make(chan unlockRequest), + lockRequests: make(chan struct{}), + holdUnlockRequests: make(chan chan heldUnlock), + lockState: make(chan bool), + changePassphrase: make(chan changePassphraseRequest), + changePassphrases: make(chan changePassphrasesRequest), + chainParams: params, + quit: make(chan struct{}), } w.NtfnServer = newNotificationServer(w) w.TxStore.NotifyUnspent = func(hash *chainhash.Hash, index uint32) { From 7cd00ae5fc3d90220256aafdfcd6453dad5692fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sat, 6 Oct 2018 16:33:43 +0300 Subject: [PATCH 04/25] wallet, rpc\legacyrpc\methods: Convert txId(string) to txHash (chainHash.Hash) --- rpc/legacyrpc/methods.go | 14 ++++++++++---- wallet/createtx.go | 9 --------- wallet/wallet.go | 12 ++++++------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 96a1cce821..2da8a0fa99 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -1497,16 +1497,22 @@ func transferTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error cmd := icmd.(*btcjson.TransferTransactionCmd) address := cmd.Address - txId := cmd.TxId + txHash, err := chainhash.NewHashFromStr(cmd.TxId) + if err != nil { + return nil, &btcjson.RPCError{ + Code: btcjson.ErrRPCDecodeHexString, + Message: "Transaction hash string decode failed: " + err.Error(), + } + } - return transferToAddress(w, address, txId, + return transferToAddress(w, address, *txHash, waddrmgr.DefaultAccountNum, 1, txrules.DefaultRelayFeePerKb) } -func transferToAddress(w *wallet.Wallet, addrStr string, txId string, +func transferToAddress(w *wallet.Wallet, addrStr string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (string, error) { - _, err := w.TransferTx(addrStr, txId, account, minconf, feeSatPerKb) //txHash + _, err := w.TransferTx(addrStr, txHash, account, minconf, feeSatPerKb) //txHash if err != nil { // TODO : check for tx is not found error // TODO : check for tx is not mature error diff --git a/wallet/createtx.go b/wallet/createtx.go index 688f0b9ee3..dc831f865d 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -96,15 +96,6 @@ func (s secretSource) GetScript(addr btcutil.Address) ([]byte, error) { return msa.Script() } -// TODO : Write summary -func (w *Wallet) txTransferToOutputs(address string, txId string, account uint32, - minconf int32, feeSatPerKB btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { - //TODO : Implement this - fmt.Printf("txTransferToOutputs") - - return nil, nil -} - // txToOutputs creates a signed transaction which includes each output from // outputs. Previous outputs to reedeem are chosen from the passed account's // UTXO set and minconf policy. An additional output may be added to return diff --git a/wallet/wallet.go b/wallet/wallet.go index bc6c081576..2e11f2e53c 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1129,7 +1129,7 @@ type ( createTxTransferRequest struct { account uint32 address string - txId string + txHash chainhash.Hash minconf int32 feeSatPerKB btcutil.Amount resp chan createTxTransferResponse @@ -1152,7 +1152,7 @@ out: txr.resp <- createTxTransferResponse{nil, err} continue } - tx, err := w.txTransferToOutputs(txr.address, txr.txId, txr.account, + tx, err := w.txTransferToOutputs(txr.address, txr.txHash, txr.account, txr.minconf, txr.feeSatPerKB) heldUnlock.release() txr.resp <- createTxTransferResponse{tx, err} @@ -1164,11 +1164,11 @@ out: } // TODO : Write summary -func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txId string, minconf int32, feeSatPerKb btcutil.Amount) (*txauthor.AuthoredTx, error) { +func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txHash chainhash.Hash, minconf int32, feeSatPerKb btcutil.Amount) (*txauthor.AuthoredTx, error) { req := createTxTransferRequest{ account: account, address: address, - txId: txId, + txHash: txHash, minconf: minconf, feeSatPerKB: feeSatPerKb, resp: make(chan createTxTransferResponse), @@ -1179,8 +1179,8 @@ func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txId str } // TODO : Write summary -func (w *Wallet) TransferTx(address string, txId string, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (*chainhash.Hash, error) { - _, err := w.CreateSimpleTxTransfer(account, address, txId, minconf, feeSatPerKb) +func (w *Wallet) TransferTx(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (*chainhash.Hash, error) { + _, err := w.CreateSimpleTxTransfer(account, address, txHash, minconf, feeSatPerKb) if err != nil { return nil, err } From c6f81a8d628ae059bd718a5bbf16cc95a5c134ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sat, 6 Oct 2018 16:36:08 +0300 Subject: [PATCH 05/25] wallet\transferTx : Add transferTx.go which includes transaction transfer logic .gitIgnore: Update to discard *.exe files --- .gitignore | 1 + wallet/transferTx.go | 120 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 wallet/transferTx.go diff --git a/.gitignore b/.gitignore index a5c7e6bb66..07000a4fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ btcwallet vendor .idea +*.exe \ No newline at end of file diff --git a/wallet/transferTx.go b/wallet/transferTx.go new file mode 100644 index 0000000000..51d295d317 --- /dev/null +++ b/wallet/transferTx.go @@ -0,0 +1,120 @@ +// TODO : License related notes + +package wallet + +import ( + "fmt" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wallet/txauthor" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" +) + +// TODO : Write summary +func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, account uint32, + minconf int32, feeSatPerKB btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { + //TODO : Implement this + fmt.Printf("txTransferToOutputs") + + chainClient, err := w.requireChainClient() + if err != nil { + return nil, err + } + + // Open a database read/write transaction and executes the function f + err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { + addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) + _ = addrmgrNs + + // Get current block's height and hash. + bs, err := chainClient.BlockStamp() + if err != nil { + return err + } + + txToBoTransferred, err := w.findTheTransaction(dbtx, txHash, account, minconf, bs) + if err != nil { + return err + } + + // Make outputs for tx to be transferred + amount := txToBoTransferred.Amount + _ = amount + + return nil + }) + + return nil, nil +} + +// findTransaction is modified from 'findEligibleOutputs' in 'createtx.go' +func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, + account uint32, minconf int32, bs *waddrmgr.BlockStamp) (wtxmgr.Credit, error) { + + addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) + + // TODO : Eventually get from post-dated transactions (POST-DATED feature) + unspent, err := w.TxStore.UnspentOutputs(txmgrNs) + if err != nil { + return wtxmgr.Credit{}, err + } + + // TODO: Eventually all of these filters (except perhaps output locking) + // should be handled by the call to UnspentOutputs (or similar). + // Because one of these filters requires matching the output script to + // the desired account, this change depends on making wtxmgr a waddrmgr + // dependancy and requesting unspent outputs for a single account. + var eligible wtxmgr.Credit + for i := range unspent { + output := &unspent[i] + + // TODO : Check this + // For post-dated cheques, we want to transfer only tx with txHash + if output.Hash != txHash { + continue + } + + // Only include this output if it meets the required number of + // confirmations. Coinbase transactions must have have reached + // maturity before their outputs may be spent. + if !confirmed(minconf, output.Height, bs.Height) { + // TODO : return a custom 'not mature' error + continue + } + if output.FromCoinBase { + target := int32(w.chainParams.CoinbaseMaturity) + if !confirmed(target, output.Height, bs.Height) { + // TODO : return a custom 'not mature coinbase' error + continue + } + } + + // Locked unspent outputs are skipped. + if w.LockedOutpoint(output.OutPoint) { + // TODO : return a custom 'locked tx' error + continue + } + + // Only include the output if it is associated with the passed + // account. + // + // TODO: Handle multisig outputs by determining if enough of the + // addresses are controlled. + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + output.PkScript, w.chainParams) + if err != nil || len(addrs) != 1 { + continue + } + _, addrAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) + if err != nil || addrAcct != account { + // TODO : return a custom 'not an owned tx' error + continue + } + eligible = *output + } + return eligible, nil +} From fcd0c93effbdf5b6d17e325121308efac7a5b17b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sat, 6 Oct 2018 23:48:16 +0300 Subject: [PATCH 06/25] wallet\author: Add NewUnsignedTransactionFromInput method for post-dated NewUnsignedTransactionFromInput will be used for creating transaction transfer. Works similar to NewUnsignedTransaction method on the same file. --- wallet/txauthor/author.go | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/wallet/txauthor/author.go b/wallet/txauthor/author.go index 467153c924..f51b7ae038 100644 --- a/wallet/txauthor/author.go +++ b/wallet/txauthor/author.go @@ -7,6 +7,7 @@ package txauthor import ( "errors" + "github.com/btcsuite/btcwallet/wtxmgr" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/txscript" @@ -152,6 +153,88 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, } } +// TODO : Summary +// TODO : Unit test +// NewUnsignedTransactionFromInput ... +// Uses credit as a first input and finds another one for the fee +func NewUnsignedTransactionFromInput(credit wtxmgr.Credit, outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, + fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { + + targetAmount := h.SumOutputValues(outputs) + estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true) + targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize) + + for { + // Fetch input only for fee + feeInputAmount, inputs, inputValues, scripts, err := fetchInputs(targetFee) + if err != nil { + return nil, err + } + + inputAmount := feeInputAmount + credit.Amount + if inputAmount < targetAmount+targetFee { + return nil, insufficientFundsError{} + } + + // We count the types of inputs, which we'll use to estimate + // the vsize of the transaction. + var nested, p2wpkh, p2pkh int + for _, pkScript := range scripts { + switch { + // If this is a p2sh output, we assume this is a + // nested P2WKH. + case txscript.IsPayToScriptHash(pkScript): + nested++ + case txscript.IsPayToWitnessPubKeyHash(pkScript): + p2wpkh++ + default: + p2pkh++ + } + } + + maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh, + nested, outputs, true) + maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) + remainingAmount := inputAmount - targetAmount + if remainingAmount < maxRequiredFee { + targetFee = maxRequiredFee + continue + } + + unsignedTransaction := &wire.MsgTx{ + Version: wire.TxVersion, + TxIn: inputs, + TxOut: outputs, + LockTime: 0, + } + changeIndex := -1 + changeAmount := inputAmount - targetAmount - maxRequiredFee + if changeAmount != 0 && !txrules.IsDustAmount(changeAmount, + txsizes.P2WPKHPkScriptSize, relayFeePerKb) { + changeScript, err := fetchChange() + if err != nil { + return nil, err + } + if len(changeScript) > txsizes.P2WPKHPkScriptSize { + return nil, errors.New("fee estimation requires change " + + "scripts no larger than P2WPKH output scripts") + } + change := wire.NewTxOut(int64(changeAmount), changeScript) + l := len(outputs) + unsignedTransaction.TxOut = append(outputs[:l:l], change) + changeIndex = l + } + + return &AuthoredTx{ + Tx: unsignedTransaction, + PrevScripts: scripts, + PrevInputValues: inputValues, + TotalInput: inputAmount, + ChangeIndex: changeIndex, + }, nil + } +} + // RandomizeOutputPosition randomizes the position of a transaction's output by // swapping it with a random output. The new index is returned. This should be // done before signing. From 5599c4e111a60467ac2d53199d6bd15ccc278247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sat, 6 Oct 2018 23:50:49 +0300 Subject: [PATCH 07/25] wallet\transferTx: Add logic for finding transaction from wallet db. --- wallet/transferTx.go | 86 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/wallet/transferTx.go b/wallet/transferTx.go index 51d295d317..f1a594c1dc 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -4,19 +4,21 @@ package wallet import ( "fmt" + "github.com/btcsuite/btcd/chaincfg" "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/txauthor" + "github.com/btcsuite/btcwallet/wallet/txrules" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" ) // TODO : Write summary func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, account uint32, - minconf int32, feeSatPerKB btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { - //TODO : Implement this + minconf int32, feeSatPerKb btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { fmt.Printf("txTransferToOutputs") chainClient, err := w.requireChainClient() @@ -24,6 +26,38 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco return nil, err } + // Find tx to be transferred + var txToBoTransferred wtxmgr.Credit + // Open a database read transaction and executes the function f + // Used to find transaction with hash 'txHash' + err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { + // Get current block's height and hash. + bs, err := chainClient.BlockStamp() + if err != nil { + return err + } + + // Find only the transaction with hash 'txHash' that belong to 'account' + // If not found any, or the found one isn't eligible, throw a relevant exception + // Eligible if : has 'minconf' confirmation & unspent + // Eventually will return only post-dated cheques + txToBoTransferred, err = w.findTheTransaction(dbtx, txHash, account, minconf, bs) + return err + }) + + amount := txToBoTransferred.Amount + // Make outputs for tx to be transferred + redeemOutput, err := makeOutput(address, amount, w.ChainParams()) + if err != nil { + return nil, err + } + + // Ensure the outputs to be created adhere to the network's consensus + // rules. + if err := txrules.CheckOutput(redeemOutput, feeSatPerKb); err != nil { + return nil, err + } + // Open a database read/write transaction and executes the function f err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) @@ -35,14 +69,35 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco return err } - txToBoTransferred, err := w.findTheTransaction(dbtx, txHash, account, minconf, bs) + eligible, err := w.findEligibleOutputs(dbtx, account, minconf, bs) if err != nil { return err } + inputSource := makeInputSource(eligible) + + changeSource := func() ([]byte, error) { + // Derive the change output script. As a hack to allow + // spending from the imported account, change addresses + // are created from account 0. + var changeAddr btcutil.Address + var err error + if account == waddrmgr.ImportedAddrAccount { + changeAddr, err = w.newChangeAddress(addrmgrNs, 0) + } else { + changeAddr, err = w.newChangeAddress(addrmgrNs, account) + } + if err != nil { + return nil, err + } + return txscript.PayToAddrScript(changeAddr) + } - // Make outputs for tx to be transferred - amount := txToBoTransferred.Amount - _ = amount + outputs := []*wire.TxOut{redeemOutput} + tx, err = txauthor.NewUnsignedTransactionFromInput(txToBoTransferred, outputs, feeSatPerKb, + inputSource, changeSource) + if err != nil { + return err + } return nil }) @@ -50,6 +105,25 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco return nil, nil } +// makeOutput creates a transaction output from a pair of address +// strings to amounts. This is used to create the outputs to include in newly +// created transactions from a JSON object describing the output destinations +// and amounts. +func makeOutput(addrStr string, amt btcutil.Amount, chainParams *chaincfg.Params) (*wire.TxOut, error) { + addr, err := btcutil.DecodeAddress(addrStr, chainParams) + if err != nil { + return nil, fmt.Errorf("cannot decode address: %s", err) + } + + pkScript, err := txscript.PayToAddrScript(addr) + if err != nil { + return nil, fmt.Errorf("cannot create txout script: %s", err) + } + + output := wire.NewTxOut(int64(amt), pkScript) + return output, nil +} + // findTransaction is modified from 'findEligibleOutputs' in 'createtx.go' func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, account uint32, minconf int32, bs *waddrmgr.BlockStamp) (wtxmgr.Credit, error) { From 420fb8be480a2ca9ae10b91276d9589e5e88d7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sun, 7 Oct 2018 00:14:23 +0300 Subject: [PATCH 08/25] wallet: Use publishTransaction in TransferTx method. txauthor\author: Added todos --- wallet/txauthor/author.go | 8 ++++++++ wallet/wallet.go | 11 +++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/wallet/txauthor/author.go b/wallet/txauthor/author.go index f51b7ae038..e2f9f325ed 100644 --- a/wallet/txauthor/author.go +++ b/wallet/txauthor/author.go @@ -155,6 +155,9 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, // TODO : Summary // TODO : Unit test +// TODO : Test zero fee scenario. It should be +// one input, one output transaction if there is no fee +// // NewUnsignedTransactionFromInput ... // Uses credit as a first input and finds another one for the fee func NewUnsignedTransactionFromInput(credit wtxmgr.Credit, outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, @@ -176,6 +179,10 @@ func NewUnsignedTransactionFromInput(credit wtxmgr.Credit, outputs []*wire.TxOut return nil, insufficientFundsError{} } + // Add script of redeem credit manually + // because fetchInputs returns input only for fee + scripts = append(scripts, credit.PkScript) + // We count the types of inputs, which we'll use to estimate // the vsize of the transaction. var nested, p2wpkh, p2pkh int @@ -192,6 +199,7 @@ func NewUnsignedTransactionFromInput(credit wtxmgr.Credit, outputs []*wire.TxOut } } + // TODO : Test and manupilate codes below maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh, nested, outputs, true) maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) diff --git a/wallet/wallet.go b/wallet/wallet.go index 2e11f2e53c..021e22bf34 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1180,17 +1180,12 @@ func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txHash c // TODO : Write summary func (w *Wallet) TransferTx(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (*chainhash.Hash, error) { - _, err := w.CreateSimpleTxTransfer(account, address, txHash, minconf, feeSatPerKb) + createdTx, err := w.CreateSimpleTxTransfer(account, address, txHash, minconf, feeSatPerKb) if err != nil { return nil, err } - // TODO : Use below code block inside CreateSimpleTxTransfer - /* for _, output := range outputs { - if err := txrules.CheckOutput(output, satPerKb); err != nil { - return nil, err - } - } */ - return nil, nil + + return w.publishTransaction(createdTx.Tx) } // CreateSimpleTx creates a new signed transaction spending unspent P2PKH From 2b6f20721d12276b4df98ae5c6923cb6040cabfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sun, 7 Oct 2018 23:51:20 +0300 Subject: [PATCH 09/25] methods: Return hash of transaction instead of dummy hash --- rpc/legacyrpc/methods.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 2da8a0fa99..658a826c46 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -1512,7 +1512,7 @@ func transferTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error func transferToAddress(w *wallet.Wallet, addrStr string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (string, error) { - _, err := w.TransferTx(addrStr, txHash, account, minconf, feeSatPerKb) //txHash + redeemTxHash, err := w.TransferTx(addrStr, txHash, account, minconf, feeSatPerKb) //txHash if err != nil { // TODO : check for tx is not found error // TODO : check for tx is not mature error @@ -1532,7 +1532,7 @@ func transferToAddress(w *wallet.Wallet, addrStr string, txHash chainhash.Hash, } } - txHashStr := "0000-0000-0000-0001" //txHash.String() + txHashStr := redeemTxHash.String() log.Infof("Successfully transferred transaction %v", txHashStr) return txHashStr, nil } From 3810cf1a7a5cce69cdea8838bda1027ae4c05e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sun, 7 Oct 2018 23:55:04 +0300 Subject: [PATCH 10/25] transferTx: Return relevant types errors, randomize change output. Transferring transaction completed. Tested transaction not found error. Need to test other flows. --- wallet/transferTx.go | 57 +++++++++++++++++++++++++++------------ wallet/txauthor/author.go | 3 ++- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/wallet/transferTx.go b/wallet/transferTx.go index f1a594c1dc..2d93e5285c 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -27,7 +27,7 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco } // Find tx to be transferred - var txToBoTransferred wtxmgr.Credit + var txToBoTransferred *wtxmgr.Credit // Open a database read transaction and executes the function f // Used to find transaction with hash 'txHash' err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { @@ -44,6 +44,9 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco txToBoTransferred, err = w.findTheTransaction(dbtx, txHash, account, minconf, bs) return err }) + if err != nil { + return nil, err + } amount := txToBoTransferred.Amount // Make outputs for tx to be transferred @@ -74,7 +77,6 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco return err } inputSource := makeInputSource(eligible) - changeSource := func() ([]byte, error) { // Derive the change output script. As a hack to allow // spending from the imported account, change addresses @@ -99,10 +101,31 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco return err } - return nil + // Randomize change position, if change exists, before signing. + // This doesn't affect the serialize size, so the change amount + // will still be valid. + if tx.ChangeIndex >= 0 { + tx.RandomizeChangePosition() + } + + return tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs}) }) + if err != nil { + return nil, err + } + + err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues) + if err != nil { + return nil, err + } + + if tx.ChangeIndex >= 0 && account == waddrmgr.ImportedAddrAccount { + changeAmount := btcutil.Amount(tx.Tx.TxOut[tx.ChangeIndex].Value) + log.Warnf("Spend from imported account produced change: moving"+ + " %v from imported account into default account.", changeAmount) + } - return nil, nil + return tx, nil } // makeOutput creates a transaction output from a pair of address @@ -126,7 +149,7 @@ func makeOutput(addrStr string, amt btcutil.Amount, chainParams *chaincfg.Params // findTransaction is modified from 'findEligibleOutputs' in 'createtx.go' func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, - account uint32, minconf int32, bs *waddrmgr.BlockStamp) (wtxmgr.Credit, error) { + account uint32, minconf int32, bs *waddrmgr.BlockStamp) (*wtxmgr.Credit, error) { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) @@ -134,7 +157,7 @@ func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, // TODO : Eventually get from post-dated transactions (POST-DATED feature) unspent, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { - return wtxmgr.Credit{}, err + return nil, err } // TODO: Eventually all of these filters (except perhaps output locking) @@ -142,11 +165,10 @@ func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, // Because one of these filters requires matching the output script to // the desired account, this change depends on making wtxmgr a waddrmgr // dependancy and requesting unspent outputs for a single account. - var eligible wtxmgr.Credit + var eligible *wtxmgr.Credit for i := range unspent { output := &unspent[i] - // TODO : Check this // For post-dated cheques, we want to transfer only tx with txHash if output.Hash != txHash { continue @@ -156,21 +178,18 @@ func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, // confirmations. Coinbase transactions must have have reached // maturity before their outputs may be spent. if !confirmed(minconf, output.Height, bs.Height) { - // TODO : return a custom 'not mature' error - continue + return nil, txNotMatureError{} } if output.FromCoinBase { target := int32(w.chainParams.CoinbaseMaturity) if !confirmed(target, output.Height, bs.Height) { - // TODO : return a custom 'not mature coinbase' error - continue + return nil, txCoinbaseNotMatureError{} } } // Locked unspent outputs are skipped. if w.LockedOutpoint(output.OutPoint) { - // TODO : return a custom 'locked tx' error - continue + return nil, txIsLockedError{} } // Only include the output if it is associated with the passed @@ -185,10 +204,14 @@ func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, } _, addrAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) if err != nil || addrAcct != account { - // TODO : return a custom 'not an owned tx' error - continue + return nil, txIsNotOwnedError{} } - eligible = *output + eligible = output } + + if eligible == nil { + return nil, txNotFoundError{} + } + return eligible, nil } diff --git a/wallet/txauthor/author.go b/wallet/txauthor/author.go index e2f9f325ed..38d984a093 100644 --- a/wallet/txauthor/author.go +++ b/wallet/txauthor/author.go @@ -160,7 +160,7 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, // // NewUnsignedTransactionFromInput ... // Uses credit as a first input and finds another one for the fee -func NewUnsignedTransactionFromInput(credit wtxmgr.Credit, outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, +func NewUnsignedTransactionFromInput(credit *wtxmgr.Credit, outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { targetAmount := h.SumOutputValues(outputs) @@ -209,6 +209,7 @@ func NewUnsignedTransactionFromInput(credit wtxmgr.Credit, outputs []*wire.TxOut continue } + // TODO : Should version change for post-dated cheqeues unsignedTransaction := &wire.MsgTx{ Version: wire.TxVersion, TxIn: inputs, From 7ece4db21b770d036655ea013c93ddad9c34a08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Wed, 10 Oct 2018 23:32:23 +0300 Subject: [PATCH 11/25] transferTx: Fix unnecessary loops --- wallet/transferTx.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/wallet/transferTx.go b/wallet/transferTx.go index 2d93e5285c..9c627a1444 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -19,7 +19,7 @@ import ( // TODO : Write summary func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { - fmt.Printf("txTransferToOutputs") + log.Infof("Transaction transfer requested. Address : %s, Tx hash: %s, Account: %d \n", address, txHash, account) chainClient, err := w.requireChainClient() if err != nil { @@ -207,11 +207,9 @@ func (w *Wallet) findTheTransaction(dbtx walletdb.ReadTx, txHash chainhash.Hash, return nil, txIsNotOwnedError{} } eligible = output - } - if eligible == nil { - return nil, txNotFoundError{} + return eligible, nil } - return eligible, nil + return nil, txNotFoundError{} } From 5d08f430ef0ada59571e3539a163aa16b1ebe0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Wed, 10 Oct 2018 23:39:14 +0300 Subject: [PATCH 12/25] wallet: Fix wrong wait group count --- wallet/wallet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/wallet.go b/wallet/wallet.go index 021e22bf34..2b8eaccbed 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -141,7 +141,7 @@ func (w *Wallet) Start() { } w.quitMu.Unlock() - w.wg.Add(2) + w.wg.Add(3) go w.txCreator() go w.txTransferCreator() go w.walletLocker() From 6e18c96c3c0a12c88961e44df00ef4a681ae9711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Thu, 11 Oct 2018 23:45:07 +0300 Subject: [PATCH 13/25] txauthor/author: Add tx to be transferred as input to created tx --- wallet/transferTx.go | 5 ++--- wallet/txauthor/author.go | 24 +++++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/wallet/transferTx.go b/wallet/transferTx.go index 9c627a1444..d768802a0d 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -19,7 +19,7 @@ import ( // TODO : Write summary func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { - log.Infof("Transaction transfer requested. Address : %s, Tx hash: %s, Account: %d \n", address, txHash, account) + log.Infof("Transaction transfer requested. Address : %s, Tx hash: %s, Account: %d", address, txHash, account) chainClient, err := w.requireChainClient() if err != nil { @@ -94,8 +94,7 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco return txscript.PayToAddrScript(changeAddr) } - outputs := []*wire.TxOut{redeemOutput} - tx, err = txauthor.NewUnsignedTransactionFromInput(txToBoTransferred, outputs, feeSatPerKb, + tx, err = txauthor.NewUnsignedTransactionFromInput(txToBoTransferred, redeemOutput, feeSatPerKb, inputSource, changeSource) if err != nil { return err diff --git a/wallet/txauthor/author.go b/wallet/txauthor/author.go index 38d984a093..09fdc400cb 100644 --- a/wallet/txauthor/author.go +++ b/wallet/txauthor/author.go @@ -160,28 +160,35 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, // // NewUnsignedTransactionFromInput ... // Uses credit as a first input and finds another one for the fee -func NewUnsignedTransactionFromInput(credit *wtxmgr.Credit, outputs []*wire.TxOut, relayFeePerKb btcutil.Amount, +func NewUnsignedTransactionFromInput(credit *wtxmgr.Credit, output *wire.TxOut, relayFeePerKb btcutil.Amount, fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { - targetAmount := h.SumOutputValues(outputs) + outputs := []*wire.TxOut{output} + + // Create input from transaction to be transferred + nextInput := wire.NewTxIn(&credit.OutPoint, nil, nil) + + targetAmount := credit.Amount estimatedSize := txsizes.EstimateVirtualSize(0, 1, 0, outputs, true) targetFee := txrules.FeeForSerializeSize(relayFeePerKb, estimatedSize) for { // Fetch input only for fee - feeInputAmount, inputs, inputValues, scripts, err := fetchInputs(targetFee) + feeInputAmount, feeInputs, feeInputValues, feeScripts, err := fetchInputs(targetFee) if err != nil { return nil, err } - inputAmount := feeInputAmount + credit.Amount - if inputAmount < targetAmount+targetFee { + // Couldn't find eligible input for fee + if feeInputAmount < targetFee { return nil, insufficientFundsError{} } - // Add script of redeem credit manually - // because fetchInputs returns input only for fee - scripts = append(scripts, credit.PkScript) + // Calculate input values from fee & transaction to be transferred + inputAmount := feeInputAmount + credit.Amount + inputs := append(feeInputs, nextInput) + inputValues := append(feeInputValues, credit.Amount) + scripts := append(feeScripts, credit.PkScript) // We count the types of inputs, which we'll use to estimate // the vsize of the transaction. @@ -199,7 +206,6 @@ func NewUnsignedTransactionFromInput(credit *wtxmgr.Credit, outputs []*wire.TxOu } } - // TODO : Test and manupilate codes below maxSignedSize := txsizes.EstimateVirtualSize(p2pkh, p2wpkh, nested, outputs, true) maxRequiredFee := txrules.FeeForSerializeSize(relayFeePerKb, maxSignedSize) From 329934586bbaaf30c9180aa94be8a1cebfbd6002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Thu, 11 Oct 2018 23:46:32 +0300 Subject: [PATCH 14/25] wallet\: Add 'transferTxError' for transaction transfer errors --- wallet/transferTxErrors.go | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 wallet/transferTxErrors.go diff --git a/wallet/transferTxErrors.go b/wallet/transferTxErrors.go new file mode 100644 index 0000000000..eacabc2f32 --- /dev/null +++ b/wallet/transferTxErrors.go @@ -0,0 +1,46 @@ +package wallet + +type TransactionTransferError interface { + error + TransactionTransferError() +} + +// Transaction not found error +type txNotFoundError struct{} + +func (txNotFoundError) Error() string { + return "transaction not found to transfer" +} +func (txNotFoundError) TransactionTransferError() {} + +// Transaction in not mature error +type txNotMatureError struct{} + +func (txNotMatureError) Error() string { + return "transaction is not mature" +} +func (txNotMatureError) TransactionTransferError() {} + +// Coinbase transaction in not mature error +type txCoinbaseNotMatureError struct{} + +func (txCoinbaseNotMatureError) Error() string { + return "Coin transaction is not mature" +} +func (txCoinbaseNotMatureError) TransactionTransferError() {} + +// transaction is locked error +type txIsLockedError struct{} + +func (txIsLockedError) Error() string { + return "Transaction is locked" +} +func (txIsLockedError) TransactionTransferError() {} + +// transaction is not owned by account error +type txIsNotOwnedError struct{} + +func (txIsNotOwnedError) Error() string { + return "Transaction is not owned by this account" +} +func (txIsNotOwnedError) TransactionTransferError() {} From 8b45f961b269ace079e908796eb13350eba64364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Thu, 11 Oct 2018 23:57:31 +0300 Subject: [PATCH 15/25] Closing issue 1 From d1aadaba1f01193630d21bbc8d6525eaf2e82d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sun, 14 Oct 2018 23:44:47 +0300 Subject: [PATCH 16/25] wallet/transferTx: Discard utxo from eligible utxo list Don't allow the utxo that we want to transfer to be selected as an input for fee After finding eligible utxo, I removed the utxo that we will transfer. --- wallet/transferTx.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wallet/transferTx.go b/wallet/transferTx.go index d768802a0d..2ef92e4034 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -76,6 +76,15 @@ func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, acco if err != nil { return err } + + // Remove transaction to be transferred from eligible inputs + // If we do not, it causes duplicate inputs error + for idx, item := range eligible { + if item.Hash == txHash { + eligible = append(eligible[:idx], eligible[idx+1:]...) + } + } + inputSource := makeInputSource(eligible) changeSource := func() ([]byte, error) { // Derive the change output script. As a hack to allow From 8555cfe4173116c4c1d92161798e98f3a3878e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Sun, 14 Oct 2018 23:46:39 +0300 Subject: [PATCH 17/25] Add readme file for use case of transaction transfer. This use case includes a successful transaction transfer. --- wallet/README-TransactionTransfer.md | 151 +++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 wallet/README-TransactionTransfer.md diff --git a/wallet/README-TransactionTransfer.md b/wallet/README-TransactionTransfer.md new file mode 100644 index 0000000000..4b5954923d --- /dev/null +++ b/wallet/README-TransactionTransfer.md @@ -0,0 +1,151 @@ +``` +btcd -C .\btcd.conf + +btcwallet -C .\btcwallet.conf --create +-> Wallet seed (SimNet) 51b63e594eafd12d1393f49936bcee0b8a54c12e506309f21c0d52af6ad0ea47 + +btcwallet -C .\btcwallet.conf + +btcctl -C .\btcctl.conf --wallet walletpassphrase "[password]" 600 + +btcctl -C .\btcctl.conf --wallet getnewaddress +-> SYYFCKLX38x6aZRsxuLmdNXSBDZ3BaJczq + +btcd -C .\btcd.conf --miningaddr=SYYFCKLX38x6aZRsxuLmdNXSBDZ3BaJczq + +btcctl -C .\btcctl.conf --wallet createnewaccount account1 + +btcctl -C .\btcctl.conf --wallet getnewaddress account1 +-> SZMEzGtDCgxmm1WKjbSUqBie3xcU7ovuyG + +btcctl -C .\btcctl.conf --wallet getblockhash 0 +-> 683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6 + +btcctl -C .\btcctl.conf --wallet getblock 683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6 +-> +{ + "hash": "683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6", + "confirmations": 1, + "strippedsize": 285, + "size": 285, + "weight": 1140, + "height": 0, + "version": 1, + "versionHex": "00000001", + "merkleroot": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + "tx": [ + "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" + ], + "time": 1401292357, + "nonce": 2, + "bits": "207fffff", + "difficulty": 1, + "previousblockhash": "0000000000000000000000000000000000000000000000000000000000000000" +} + +btcctl -C .\btcctl.conf generate 109 +-> +[ + "2a666bba893f07ce7e9ebfa2d0aa0379b0b54bc6487b3715ab5200b74fda9154", + "1641c6a0d5916cfc2f70b7c89fcd27dfb85fe11886516dc6f1de36b8f4761212", + .... +] + +btcctl -C .\btcctl.conf --wallet getblockhash 8 +-> 20c703d4ff8b515c634073ebe99cc01ac9f7739dee0507f89d01dbd0fe76c853 + +btcctl -C .\btcctl.conf --wallet getblock 20c703d4ff8b515c634073ebe99cc01ac9f7739dee0507f89d01dbd0fe76c853 +-> +{ + "hash": "20c703d4ff8b515c634073ebe99cc01ac9f7739dee0507f89d01dbd0fe76c853", + "confirmations": 100, + "strippedsize": 188, + "size": 188, + "weight": 752, + "height": 8, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "de14e6d076a217558dd23191ee6ae3b652297d359f92c6973f3139fe8d757359", + "tx": [ + "de14e6d076a217558dd23191ee6ae3b652297d359f92c6973f3139fe8d757359" + ], + "time": 1539433122, + "nonce": 0, + "bits": "207fffff", + "difficulty": 1, + "previousblockhash": "4d2d4f8d266c329a24576ab314edace636e376b0cb33d661b18e52c03085f06a", + "nextblockhash": "3294318179979673976745ab8b83bf5fc33c0399aa42db0299b33dab81ded9ac" +} + +btcctl -C .\btcctl.conf --wallet listunspent +-> +[ + { + "txid": "e8511bd42a9bbdac69655fd3bb0e5cb2f9c42cc9b51476c65682ba18a18e4c0e", + "vout": 0, + "address": "SYYFCKLX38x6aZRsxuLmdNXSBDZ3BaJczq", + "account": "default", + "scriptPubKey": "76a9147b5b07b286ef494549db8630aeb37301bf99663c88ac", + "amount": 50, + "confirmations": 100, + "spendable": true + }, + { + "txid": "de14e6d076a217558dd23191ee6ae3b652297d359f92c6973f3139fe8d757359", + "vout": 0, + "address": "SYYFCKLX38x6aZRsxuLmdNXSBDZ3BaJczq", + "account": "default", + "scriptPubKey": "76a9147b5b07b286ef494549db8630aeb37301bf99663c88ac", + "amount": 50, + "confirmations": 101, + "spendable": true + } +] + +btcctl -C .\btcctl.conf --wallet transfertransaction SZMEzGtDCgxmm1WKjbSUqBie3xcU7ovuyG de14e6d076a217558dd23191ee6ae3b652297d359f92c6973f3139fe8d757359 +-> d7ff90d4a3a07148446cc98cae786efe3e394e9660614fac262ab1354575f4c4 + +btcctl -C .\btcctl.conf --wallet listunspent +-> [] (bug here) + +btcctl -C .\btcctl.conf generate 1 +-> +[ + "47b7d70a2088854f80a39fa57f392715c2ad7abf8f04e2c7938f6fc683f618c8" +] + +btcctl -C .\btcctl.conf --wallet listunspent +-> +[ + { + "txid": "d7ff90d4a3a07148446cc98cae786efe3e394e9660614fac262ab1354575f4c4", + "vout": 1, + "address": "SZMEzGtDCgxmm1WKjbSUqBie3xcU7ovuyG", + "account": "account1", + "scriptPubKey": "76a914843e682e2cad833c2b4056a473423cf4458f38f388ac", + "amount": 50, + "confirmations": 1, + "spendable": true + }, + { + "txid": "d7ff90d4a3a07148446cc98cae786efe3e394e9660614fac262ab1354575f4c4", + "vout": 0, + "address": "sb1qt05j4gu54adsxrvqfwg9e2wfwn2yzazy39x8hm", + "account": "default", + "scriptPubKey": "00145be92aa394af5b030d804b905ca9c974d4417444", + "amount": 49.99999627, + "confirmations": 1, + "spendable": true + }, + { + "txid": "33c956a67f0d9423d376fc8fcb27426da727baf162b63e501ed8bb4ff4679aee", + "vout": 0, + "address": "SYYFCKLX38x6aZRsxuLmdNXSBDZ3BaJczq", + "account": "default", + "scriptPubKey": "76a9147b5b07b286ef494549db8630aeb37301bf99663c88ac", + "amount": 50, + "confirmations": 100, + "spendable": true + } +] +``` \ No newline at end of file From d48ab3987788ce7e30356c43e9963d07e65a722d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Mon, 15 Oct 2018 00:00:12 +0300 Subject: [PATCH 18/25] Update README.md Add link of read me file for transaction transfer use case --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 8a68448b29..e718aec3b8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +Check use case for transferring a transaction as a whole +[Transaction transfer use case](/wallet/README-TransactionTransfer.md) + btcwallet ========= From 337b0a6173483ca73335a6b3ca7e466a4e1773c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Wed, 24 Oct 2018 19:30:53 +0300 Subject: [PATCH 19/25] wallet/txtransfer/author: Add newCoincaseTransaction method Add readme.md for transfer transaction. --- .../README-SampleUsage.md} | 62 ++++++++++++++++++ wallet/txtransfer/README.md | 25 ++++++++ wallet/txtransfer/author.go | 63 +++++++++++++++++++ 3 files changed, 150 insertions(+) rename wallet/{README-TransactionTransfer.md => txtransfer/README-SampleUsage.md} (63%) create mode 100644 wallet/txtransfer/README.md create mode 100644 wallet/txtransfer/author.go diff --git a/wallet/README-TransactionTransfer.md b/wallet/txtransfer/README-SampleUsage.md similarity index 63% rename from wallet/README-TransactionTransfer.md rename to wallet/txtransfer/README-SampleUsage.md index 4b5954923d..d8b80ba6ab 100644 --- a/wallet/README-TransactionTransfer.md +++ b/wallet/txtransfer/README-SampleUsage.md @@ -148,4 +148,66 @@ btcctl -C .\btcctl.conf --wallet listunspent "spendable": true } ] + +btcctl -C .\btcctl.conf --wallet gettransaction d7ff90d4a3a07148446cc98cae786efe3e394e9660614fac262ab1354575f4c4 +(This tx includes transferred transaction and the change) +-> +{ + "amount": 50, + "fee": 0.00000373, + "confirmations": 1, + "blockhash": "47b7d70a2088854f80a39fa57f392715c2ad7abf8f04e2c7938f6fc683f618c8", + "blockindex": 0, + "blocktime": 1539549532, + "txid": "d7ff90d4a3a07148446cc98cae786efe3e394e9660614fac262ab1354575f4c4", + "walletconflicts": [], + "time": 1539549533, + "timereceived": 1539549533, + "details": [ + { + "account": "", + "amount": -100, + "category": "send", + "fee": 0.00000373, + "vout": 0 + }, + { + "account": "account1", + "address": "SZMEzGtDCgxmm1WKjbSUqBie3xcU7ovuyG", + "amount": 50, + "category": "receive", + "vout": 1 + } + ], + "hex": "01000000020e4c8ea118ba8256c67614b5c92cc4f9b25c0ebbd35f6569acbd9b2ad41b51e8000000006a4730440220101c9a344296bd8fb49e48878b0342ba578ab92c38269c72915d79b53763c0440220446b54721493bff502a39eba0b69b5faa4a349bc1f7a6ff7cbbaabb00f4c3f9d01210223ac6a75f94e60de9ad1642982170c958f7eba2d8640830b28fb67e1fe57561dffffffff5973758dfe39313f97c6929f357d2952b6e36aee9131d28d5517a276d0e614de000000006b483045022100856c05b2136a03362aa5f44892caabd90efac09780409deb19955c1bca36858d02205a7563e0f265ab55c4c75e5508fa2e987c8977873a30aa62788985cd5fccc05901210223ac6a75f94e60de9ad1642982170c958f7eba2d8640830b28fb67e1fe57561dffffffff028bf0052a010000001600145be92aa394af5b030d804b905ca9c974d441744400f2052a010000001976a914843e682e2cad833c2b4056a473423cf4458f38f388ac00000000" +} + +btcctl -C .\btcctl.conf --wallet gettransaction 33c956a67f0d9423d376fc8fcb27426da727baf162b63e501ed8bb4ff4679aee +(This is the reward of 10. block) +-> +{ + "amount": 50, + "confirmations": 100, + "blockhash": "5887b49fd44146eb24447ae3c8eeb93bb80c76f8b1e931e445ecc1066427cb59", + "blockindex": 0, + "blocktime": 1539433122, + "txid": "33c956a67f0d9423d376fc8fcb27426da727baf162b63e501ed8bb4ff4679aee", + "walletconflicts": [], + "time": 1539433120, + "timereceived": 1539433120, + "details": [ + { + "account": "default", + "address": "SYYFCKLX38x6aZRsxuLmdNXSBDZ3BaJczq", + "amount": 50, + "category": "generate", + "vout": 0 + } + ], + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff165a08830f409bcb227e3c0b2f503253482f627463642fffffffff0100f2052a010000001976a9147b5b07b286ef494549db8630aeb37301bf99663c88ac00000000" +} + +(Block height of 47b7d70a2088854f80a39fa57f392715c2ad7abf8f04e2c7938f6fc683f618c8 : 109) +(Block height of 5887b49fd44146eb24447ae3c8eeb93bb80c76f8b1e931e445ecc1066427cb59 : 10) + ``` \ No newline at end of file diff --git a/wallet/txtransfer/README.md b/wallet/txtransfer/README.md new file mode 100644 index 0000000000..34d7d91e9e --- /dev/null +++ b/wallet/txtransfer/README.md @@ -0,0 +1,25 @@ +// TODO : Change this for transaction transfer rules +(Taken from https://en.bitcoin.it/wiki/Protocol_rules) + +These messages hold a single transaction. + +Check syntactic correctness +Make sure neither in or out lists are empty +Size in bytes <= MAX_BLOCK_SIZE +Each output value, as well as the total, must be in legal money range +Make sure none of the inputs have hash=0, n=-1 (coinbase transactions) +Check that nLockTime <= INT_MAX, size in bytes >= 100, and sig opcount <= 2 +Reject "nonstandard" transactions: scriptSig doing anything other than pushing numbers on the stack, or scriptPubkey not matching the two usual forms +Reject if we already have matching tx in the pool, or in a block in the main branch +For each input, if the referenced output exists in any other tx in the pool, reject this transaction. +For each input, look in the main branch and the transaction pool to find the referenced output transaction. If the output transaction is missing for any input, this will be an orphan transaction. Add to the orphan transactions, if a matching transaction is not in there already. +For each input, if the referenced output transaction is coinbase (i.e. only 1 input, with hash=0, n=-1), it must have at least COINBASE_MATURITY (100) confirmations; else reject this transaction +For each input, if the referenced output does not exist (e.g. never existed or has already been spent), reject this transaction +Using the referenced output transactions to get input values, check that each input value, as well as the sum, are in legal money range +Reject if the sum of input values < sum of output values +Reject if transaction fee (defined as sum of input values minus sum of output values) would be too low to get into an empty block +Verify the scriptPubKey accepts for each input; reject if any are bad +Add to transaction pool +"Add to wallet if mine" +Relay transaction to peers +For each orphan transaction that uses this one as one of its inputs, run all these steps (including this one) recursively on that orphan \ No newline at end of file diff --git a/wallet/txtransfer/author.go b/wallet/txtransfer/author.go new file mode 100644 index 0000000000..5598657708 --- /dev/null +++ b/wallet/txtransfer/author.go @@ -0,0 +1,63 @@ +package txtransfer + +import ( + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +const ( + // TODO : This one should be in btcd/wire + TransferTxVersion = 99 + coincaseTxFlags = "/POSTDATED/" + + // TODO : This one should be in btcd/blockchain + CoincaseWitnessDataLen = 32 +) + +type AuthoredTx struct { + Tx *wire.MsgTx +} + +func createCoincaseScript() ([]byte, error) { + return txscript.NewScriptBuilder().AddData([]byte(coincaseTxFlags)).Script() +} + +// Reference : btcsuite\btcd\mining\mining.go:253 +func newCoincaseTransaction(addr btcutil.Address, amount int64, lockTime uint32) (*btcutil.Tx, error) { + var pkScript []byte + var err error + + pkScript, err = txscript.PayToAddrScript(addr) + if err != nil { + return nil, err + } + + postDatedScript, err := createCoincaseScript() + if err != nil { + return nil, err + } + + tx := wire.NewMsgTx(TransferTxVersion) + + tx.AddTxIn(&wire.TxIn{ + // Transfer transactions have no inputs, so previous outpoint is + // zero hash and max index. + PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, + wire.MaxPrevOutIndex), + SignatureScript: postDatedScript, + Sequence: wire.MaxTxInSequenceNum, + }) + tx.AddTxOut(&wire.TxOut{ + Value: amount, + PkScript: pkScript, + }) + + tx.LockTime = lockTime + + // Reference : btcsuite\btcd\mining\mining.go:805 + // TODO : Check segwit related codes + + return btcutil.NewTx(tx), nil +} From e78bdde76139a2bad8f5e63f6e998cad53ad38a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Thu, 25 Oct 2018 18:30:28 +0300 Subject: [PATCH 20/25] wallet: Move transaction transfer related codes to transferTx.go --- README.md | 2 +- wallet/transferTx.go | 68 ++++++++++++++++++++++++++++++++++++++++++++ wallet/wallet.go | 64 ----------------------------------------- 3 files changed, 69 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index e718aec3b8..3a56663cbe 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ Check use case for transferring a transaction as a whole -[Transaction transfer use case](/wallet/README-TransactionTransfer.md) +[Transaction transfer use case](/wallet/txtransfer/README-SampleUsage.md) btcwallet ========= diff --git a/wallet/transferTx.go b/wallet/transferTx.go index 2ef92e4034..6ae8c35024 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -16,6 +16,74 @@ import ( "github.com/btcsuite/btcwallet/wtxmgr" ) +func (w *Wallet) NewUnsignedTransactionFromCoincase(coincase *btcutil.Tx) { + +} + +// TODO : Write summary +type ( + createTxTransferRequest struct { + account uint32 + address string + txHash chainhash.Hash + minconf int32 + feeSatPerKB btcutil.Amount + resp chan createTxTransferResponse + } + createTxTransferResponse struct { + tx *txauthor.AuthoredTx + err error + } +) + +// TODO : Write summary +func (w *Wallet) txTransferCreator() { + quit := w.quitChan() +out: + for { + select { + case txr := <-w.createTxTransferRequests: + heldUnlock, err := w.holdUnlock() + if err != nil { + txr.resp <- createTxTransferResponse{nil, err} + continue + } + tx, err := w.txTransferToOutputs(txr.address, txr.txHash, txr.account, + txr.minconf, txr.feeSatPerKB) + heldUnlock.release() + txr.resp <- createTxTransferResponse{tx, err} + case <-quit: + break out + } + } + w.wg.Done() +} + +// TODO : Write summary +func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txHash chainhash.Hash, minconf int32, feeSatPerKb btcutil.Amount) (*txauthor.AuthoredTx, error) { + req := createTxTransferRequest{ + account: account, + address: address, + txHash: txHash, + minconf: minconf, + feeSatPerKB: feeSatPerKb, + resp: make(chan createTxTransferResponse), + } + w.createTxTransferRequests <- req + resp := <-req.resp + return resp.tx, resp.err +} + +// TODO : Write summary +func (w *Wallet) TransferTx(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (*chainhash.Hash, error) { + createdTx, err := w.CreateSimpleTxTransfer(account, address, txHash, minconf, feeSatPerKb) + if err != nil { + return nil, err + } + + return w.publishTransaction(createdTx.Tx) +} + // TODO : Write summary func (w *Wallet) txTransferToOutputs(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (tx *txauthor.AuthoredTx, err error) { diff --git a/wallet/wallet.go b/wallet/wallet.go index 2b8eaccbed..9b179915c7 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1124,70 +1124,6 @@ out: w.wg.Done() } -// TODO : Write summary -type ( - createTxTransferRequest struct { - account uint32 - address string - txHash chainhash.Hash - minconf int32 - feeSatPerKB btcutil.Amount - resp chan createTxTransferResponse - } - createTxTransferResponse struct { - tx *txauthor.AuthoredTx - err error - } -) - -// TODO : Write summary -func (w *Wallet) txTransferCreator() { - quit := w.quitChan() -out: - for { - select { - case txr := <-w.createTxTransferRequests: - heldUnlock, err := w.holdUnlock() - if err != nil { - txr.resp <- createTxTransferResponse{nil, err} - continue - } - tx, err := w.txTransferToOutputs(txr.address, txr.txHash, txr.account, - txr.minconf, txr.feeSatPerKB) - heldUnlock.release() - txr.resp <- createTxTransferResponse{tx, err} - case <-quit: - break out - } - } - w.wg.Done() -} - -// TODO : Write summary -func (w *Wallet) CreateSimpleTxTransfer(account uint32, address string, txHash chainhash.Hash, minconf int32, feeSatPerKb btcutil.Amount) (*txauthor.AuthoredTx, error) { - req := createTxTransferRequest{ - account: account, - address: address, - txHash: txHash, - minconf: minconf, - feeSatPerKB: feeSatPerKb, - resp: make(chan createTxTransferResponse), - } - w.createTxTransferRequests <- req - resp := <-req.resp - return resp.tx, resp.err -} - -// TODO : Write summary -func (w *Wallet) TransferTx(address string, txHash chainhash.Hash, account uint32, minconf int32, feeSatPerKb btcutil.Amount) (*chainhash.Hash, error) { - createdTx, err := w.CreateSimpleTxTransfer(account, address, txHash, minconf, feeSatPerKb) - if err != nil { - return nil, err - } - - return w.publishTransaction(createdTx.Tx) -} - // CreateSimpleTx creates a new signed transaction spending unspent P2PKH // outputs with at laest minconf confirmations spending to any number of // address/amount pairs. Change and an appropriate transaction fee are From c6115d7a55233019683698fea68f8a97ca5bac07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Thu, 25 Oct 2018 21:52:07 +0300 Subject: [PATCH 21/25] wallet/postdated: Add post dated transaction implementations --- wallet/postdated.go | 256 ++++++++++++++++++++++++++++++++++++ wallet/transferTx.go | 4 - wallet/txtransfer/author.go | 63 --------- wallet/wallet.go | 3 +- 4 files changed, 258 insertions(+), 68 deletions(-) create mode 100644 wallet/postdated.go delete mode 100644 wallet/txtransfer/author.go diff --git a/wallet/postdated.go b/wallet/postdated.go new file mode 100644 index 0000000000..61e1c73d87 --- /dev/null +++ b/wallet/postdated.go @@ -0,0 +1,256 @@ +// TODO : License related notes + +package wallet + +import ( + "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/txauthor" + "github.com/btcsuite/btcwallet/wallet/txrules" + "github.com/btcsuite/btcwallet/walletdb" +) + +//// Post-dated related codes + +type SendPostDatedToAddressCmd struct { + Address string + Amount int64 + LockTime uint32 +} + +func NewSendPostDatedToAddressCmd(address string, amount int64, lockTime uint32) *SendPostDatedToAddressCmd { + return &SendPostDatedToAddressCmd{ + Address: address, + Amount: amount, + LockTime: lockTime, + } +} + +func sendPostDatedTransaction(icmd interface{}, w *Wallet) (interface{}, error) { + cmd := icmd.(*SendPostDatedToAddressCmd) + return sendPostDated(w, cmd.Address, cmd.Amount, cmd.LockTime, waddrmgr.DefaultAccountNum) +} + +func sendPostDated(w *Wallet, addrStr string, amount int64, lockTime uint32, + account uint32) (string, error) { + + redeemTxHash, _ := w.SendPostDated(addrStr, amount, lockTime, account) //txHash + // TODO : Check for error + + txHashStr := redeemTxHash.String() + log.Infof("Successfully transferred transaction %v", txHashStr) + return txHashStr, nil +} + +// Entry point +func (w *Wallet) SendPostDated(addrStr string, amount int64, lockTime uint32, + account uint32) (*chainhash.Hash, error) { + createdTx, err := w.CreateSimplePostDatedTransfer(addrStr, amount, lockTime, account) + if err != nil { + return nil, err + } + + return w.publishTransaction(createdTx.Tx) +} + +type ( + createPostDatedTxRequest struct { + account uint32 + address string + amount int64 + lockTime uint32 + feeSatPerKB btcutil.Amount + resp chan createPostDatedTxResponse + } + createPostDatedTxResponse struct { + tx *txauthor.AuthoredTx + err error + } +) + +func (w *Wallet) CreateSimplePostDatedTransfer(address string, amount int64, lockTime uint32, + account uint32) (*txauthor.AuthoredTx, error) { + req := createPostDatedTxRequest{ + account: account, + address: address, + lockTime: lockTime, + amount: amount, + resp: make(chan createPostDatedTxResponse), + } + + // TODO : use channels instead of direct call + //w.createPostDatedTxRequests <- req + //resp := <-req.resp + resp := w.createPostDatedTx(req) + return resp.tx, resp.err +} + +// TODO : move & call this +func (w *Wallet) postDatedTxCreator() { + quit := w.quitChan() +out: + for { + select { + case txr := <-w.createPostDatedTxRequests: + heldUnlock, err := w.holdUnlock() + if err != nil { + txr.resp <- createPostDatedTxResponse{nil, err} + continue + } + res := w.createPostDatedTx(txr) + heldUnlock.release() + txr.resp <- res + case <-quit: + break out + } + } + w.wg.Done() +} + +const ( + // TODO : This one should be in btcd/wire + TransferTxVersion = 99 + coincaseTxFlags = "/POSTDATED/" + + // TODO : This one should be in btcd/blockchain + CoincaseWitnessDataLen = 32 + + // TODO : Need to be in wire package + PostDatedTxVersion = 2 +) + +func createCoincaseScript() ([]byte, error) { + return txscript.NewScriptBuilder().AddData([]byte(coincaseTxFlags)).Script() +} + +// Reference : btcsuite\btcd\mining\mining.go:253 +func newCoincaseTransaction(coincaseScript []byte, amount int64, lockTime uint32) ( + *btcutil.Tx, error) { + var err error + + postDatedScript, err := createCoincaseScript() + if err != nil { + return nil, err + } + + tx := wire.NewMsgTx(TransferTxVersion) + + tx.AddTxIn(&wire.TxIn{ + // Transfer transactions have no inputs, so previous outpoint is + // zero hash and max index. + PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, + wire.MaxPrevOutIndex), + SignatureScript: postDatedScript, + Sequence: wire.MaxTxInSequenceNum, + }) + tx.AddTxOut(&wire.TxOut{ + Value: amount, + PkScript: coincaseScript, + }) + + tx.LockTime = lockTime + + // Reference : btcsuite\btcd\mining\mining.go:805 + // TODO : Check segwit related codes + + return btcutil.NewTx(tx), nil +} + +func (w *Wallet) createCoincase( + dbtx walletdb.ReadWriteTx, account uint32, + amount int64, lockTime uint32) (coincaseTx *btcutil.Tx, err error) { + + addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) + + // Create coincase + coincaseSource := func() ([]byte, error) { + var coincaseAddr btcutil.Address + var err error + if account == waddrmgr.ImportedAddrAccount { + coincaseAddr, err = w.newChangeAddress(addrmgrNs, 0) + } else { + coincaseAddr, err = w.newChangeAddress(addrmgrNs, account) + } + if err != nil { + return nil, err + } + return txscript.PayToAddrScript(coincaseAddr) + } + coincaseScript, err := coincaseSource() + if err != nil { + return + } + + coincaseTx, err = newCoincaseTransaction(coincaseScript, amount, lockTime) + return +} + +type AuthoredPostDatedTx struct { + Tx *wire.MsgTx + PrevScripts [][]byte + PrevInputValues []btcutil.Amount +} + +func NewUnsignedTransactionFromCoincase(coincaseTx *btcutil.Tx, output *wire.TxOut) (*AuthoredPostDatedTx, error) { + // Create unsigned tx + unsignedTransaction := &wire.MsgTx{ + Version: PostDatedTxVersion, + } + + outpoint := wire.NewOutPoint(coincaseTx.Hash(), 0) + txIn := wire.NewTxIn(outpoint, nil, nil) + + unsignedTransaction.AddTxIn(txIn) + unsignedTransaction.AddTxOut(output) + unsignedTransaction.LockTime = coincaseTx.MsgTx().LockTime + + // Get amount from coincase + amount := btcutil.Amount(coincaseTx.MsgTx().TxOut[0].Value) + currentInputValues := make([]btcutil.Amount, 0, 1) + currentInputValues = append(currentInputValues, amount) + + // Get pkScript from coincase + currentScripts := make([][]byte, 0, 1) + currentScripts = append(currentScripts, coincaseTx.MsgTx().TxOut[0].PkScript) + + return &AuthoredPostDatedTx{ + Tx: unsignedTransaction, + PrevScripts: currentScripts, + PrevInputValues: currentInputValues, + }, nil +} + +// +func (w *Wallet) createPostDatedTx(req createPostDatedTxRequest) createPostDatedTxResponse { + amount := btcutil.Amount(req.amount) + redeemOutput, err := makeOutput(req.address, amount, w.ChainParams()) + if err != nil { + return createPostDatedTxResponse{nil, err} + } + + if err := txrules.CheckOutput(redeemOutput, req.feeSatPerKB); err != nil { + return createPostDatedTxResponse{nil, err} + } + + err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { + // Create coincase tx + coincaseTx, err := w.createCoincase(dbtx, req.account, req.amount, req.lockTime) + if err != nil { + return err + } + + NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) + + // TODO : Continue to implementation + return nil + }) + if err != nil { + return createPostDatedTxResponse{nil, err} + } + + // TODO : Continue to implementation + return createPostDatedTxResponse{nil, nil} +} diff --git a/wallet/transferTx.go b/wallet/transferTx.go index 6ae8c35024..e7111083bb 100644 --- a/wallet/transferTx.go +++ b/wallet/transferTx.go @@ -16,10 +16,6 @@ import ( "github.com/btcsuite/btcwallet/wtxmgr" ) -func (w *Wallet) NewUnsignedTransactionFromCoincase(coincase *btcutil.Tx) { - -} - // TODO : Write summary type ( createTxTransferRequest struct { diff --git a/wallet/txtransfer/author.go b/wallet/txtransfer/author.go deleted file mode 100644 index 5598657708..0000000000 --- a/wallet/txtransfer/author.go +++ /dev/null @@ -1,63 +0,0 @@ -package txtransfer - -import ( - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" -) - -const ( - // TODO : This one should be in btcd/wire - TransferTxVersion = 99 - coincaseTxFlags = "/POSTDATED/" - - // TODO : This one should be in btcd/blockchain - CoincaseWitnessDataLen = 32 -) - -type AuthoredTx struct { - Tx *wire.MsgTx -} - -func createCoincaseScript() ([]byte, error) { - return txscript.NewScriptBuilder().AddData([]byte(coincaseTxFlags)).Script() -} - -// Reference : btcsuite\btcd\mining\mining.go:253 -func newCoincaseTransaction(addr btcutil.Address, amount int64, lockTime uint32) (*btcutil.Tx, error) { - var pkScript []byte - var err error - - pkScript, err = txscript.PayToAddrScript(addr) - if err != nil { - return nil, err - } - - postDatedScript, err := createCoincaseScript() - if err != nil { - return nil, err - } - - tx := wire.NewMsgTx(TransferTxVersion) - - tx.AddTxIn(&wire.TxIn{ - // Transfer transactions have no inputs, so previous outpoint is - // zero hash and max index. - PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, - wire.MaxPrevOutIndex), - SignatureScript: postDatedScript, - Sequence: wire.MaxTxInSequenceNum, - }) - tx.AddTxOut(&wire.TxOut{ - Value: amount, - PkScript: pkScript, - }) - - tx.LockTime = lockTime - - // Reference : btcsuite\btcd\mining\mining.go:805 - // TODO : Check segwit related codes - - return btcutil.NewTx(tx), nil -} diff --git a/wallet/wallet.go b/wallet/wallet.go index 9b179915c7..9a5f268fc0 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -98,7 +98,8 @@ type Wallet struct { createTxRequests chan createTxRequest // Channel for transaction transfer requests - createTxTransferRequests chan createTxTransferRequest + createTxTransferRequests chan createTxTransferRequest + createPostDatedTxRequests chan createPostDatedTxRequest // Channels for the manager locker. unlockRequests chan unlockRequest From 7e187e06c981df3cac5d7d314332e924f840e017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Mon, 29 Oct 2018 23:52:38 +0300 Subject: [PATCH 22/25] Add&Refactor post dated tx related implementations. --- rpc/legacyrpc/methods.go | 14 ++++ wallet/postdated.go | 143 +++++---------------------------------- wallet/wallet.go | 50 +++++++------- 3 files changed, 58 insertions(+), 149 deletions(-) diff --git a/rpc/legacyrpc/methods.go b/rpc/legacyrpc/methods.go index 658a826c46..f14d945b3e 100644 --- a/rpc/legacyrpc/methods.go +++ b/rpc/legacyrpc/methods.go @@ -101,6 +101,7 @@ var rpcHandlers = map[string]struct { "lockunspent": {handler: lockUnspent}, "sendfrom": {handlerWithChain: sendFrom}, "sendmany": {handler: sendMany}, + "sendPostDatedTx": {handler: sendPostDatedTx}, "transfertransaction": {handler: transferTransaction}, "sendtoaddress": {handler: sendToAddress}, "settxfee": {handler: setTxFee}, @@ -1493,6 +1494,19 @@ func sendMany(icmd interface{}, w *wallet.Wallet) (interface{}, error) { return sendPairs(w, pairs, account, minConf, txrules.DefaultRelayFeePerKb) } +// TODO : write summary +func sendPostDatedTx(icmd interface{}, w *wallet.Wallet) (interface{}, error) { + cmd := icmd.(*btcjson.SendPostDatedTxCmd) + + redeemTxHash, _ := w.SendPostDated(cmd.Address, cmd.Amount, cmd.LockTime, waddrmgr.DefaultAccountNum) //txHash + // TODO : Check for error + + txHashStr := redeemTxHash.String() + log.Infof("Successfully transferred transaction %v", txHashStr) + return txHashStr, nil +} + +// TODO : write summary func transferTransaction(icmd interface{}, w *wallet.Wallet) (interface{}, error) { cmd := icmd.(*btcjson.TransferTransactionCmd) diff --git a/wallet/postdated.go b/wallet/postdated.go index 61e1c73d87..189b574d9c 100644 --- a/wallet/postdated.go +++ b/wallet/postdated.go @@ -4,51 +4,17 @@ package wallet import ( "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/txauthor" "github.com/btcsuite/btcwallet/wallet/txrules" - "github.com/btcsuite/btcwallet/walletdb" ) -//// Post-dated related codes - -type SendPostDatedToAddressCmd struct { - Address string - Amount int64 - LockTime uint32 -} - -func NewSendPostDatedToAddressCmd(address string, amount int64, lockTime uint32) *SendPostDatedToAddressCmd { - return &SendPostDatedToAddressCmd{ - Address: address, - Amount: amount, - LockTime: lockTime, - } -} - -func sendPostDatedTransaction(icmd interface{}, w *Wallet) (interface{}, error) { - cmd := icmd.(*SendPostDatedToAddressCmd) - return sendPostDated(w, cmd.Address, cmd.Amount, cmd.LockTime, waddrmgr.DefaultAccountNum) -} - -func sendPostDated(w *Wallet, addrStr string, amount int64, lockTime uint32, - account uint32) (string, error) { - - redeemTxHash, _ := w.SendPostDated(addrStr, amount, lockTime, account) //txHash - // TODO : Check for error - - txHashStr := redeemTxHash.String() - log.Infof("Successfully transferred transaction %v", txHashStr) - return txHashStr, nil -} - -// Entry point +// wallet/wallet.go func (w *Wallet) SendPostDated(addrStr string, amount int64, lockTime uint32, account uint32) (*chainhash.Hash, error) { - createdTx, err := w.CreateSimplePostDatedTransfer(addrStr, amount, lockTime, account) + createdTx, err := w.createSimplePostDatedTx(addrStr, amount, lockTime, account) if err != nil { return nil, err } @@ -56,6 +22,7 @@ func (w *Wallet) SendPostDated(addrStr string, amount int64, lockTime uint32, return w.publishTransaction(createdTx.Tx) } +// wallet/wallet.go type ( createPostDatedTxRequest struct { account uint32 @@ -71,7 +38,8 @@ type ( } ) -func (w *Wallet) CreateSimplePostDatedTransfer(address string, amount int64, lockTime uint32, +// wallet/wallet.go +func (w *Wallet) createSimplePostDatedTx(address string, amount int64, lockTime uint32, account uint32) (*txauthor.AuthoredTx, error) { req := createPostDatedTxRequest{ account: account, @@ -88,7 +56,7 @@ func (w *Wallet) CreateSimplePostDatedTransfer(address string, amount int64, loc return resp.tx, resp.err } -// TODO : move & call this +// TODO : move to wallet/wallet.go func (w *Wallet) postDatedTxCreator() { quit := w.quitChan() out: @@ -110,84 +78,6 @@ out: w.wg.Done() } -const ( - // TODO : This one should be in btcd/wire - TransferTxVersion = 99 - coincaseTxFlags = "/POSTDATED/" - - // TODO : This one should be in btcd/blockchain - CoincaseWitnessDataLen = 32 - - // TODO : Need to be in wire package - PostDatedTxVersion = 2 -) - -func createCoincaseScript() ([]byte, error) { - return txscript.NewScriptBuilder().AddData([]byte(coincaseTxFlags)).Script() -} - -// Reference : btcsuite\btcd\mining\mining.go:253 -func newCoincaseTransaction(coincaseScript []byte, amount int64, lockTime uint32) ( - *btcutil.Tx, error) { - var err error - - postDatedScript, err := createCoincaseScript() - if err != nil { - return nil, err - } - - tx := wire.NewMsgTx(TransferTxVersion) - - tx.AddTxIn(&wire.TxIn{ - // Transfer transactions have no inputs, so previous outpoint is - // zero hash and max index. - PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, - wire.MaxPrevOutIndex), - SignatureScript: postDatedScript, - Sequence: wire.MaxTxInSequenceNum, - }) - tx.AddTxOut(&wire.TxOut{ - Value: amount, - PkScript: coincaseScript, - }) - - tx.LockTime = lockTime - - // Reference : btcsuite\btcd\mining\mining.go:805 - // TODO : Check segwit related codes - - return btcutil.NewTx(tx), nil -} - -func (w *Wallet) createCoincase( - dbtx walletdb.ReadWriteTx, account uint32, - amount int64, lockTime uint32) (coincaseTx *btcutil.Tx, err error) { - - addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) - - // Create coincase - coincaseSource := func() ([]byte, error) { - var coincaseAddr btcutil.Address - var err error - if account == waddrmgr.ImportedAddrAccount { - coincaseAddr, err = w.newChangeAddress(addrmgrNs, 0) - } else { - coincaseAddr, err = w.newChangeAddress(addrmgrNs, account) - } - if err != nil { - return nil, err - } - return txscript.PayToAddrScript(coincaseAddr) - } - coincaseScript, err := coincaseSource() - if err != nil { - return - } - - coincaseTx, err = newCoincaseTransaction(coincaseScript, amount, lockTime) - return -} - type AuthoredPostDatedTx struct { Tx *wire.MsgTx PrevScripts [][]byte @@ -235,18 +125,19 @@ func (w *Wallet) createPostDatedTx(req createPostDatedTxRequest) createPostDated return createPostDatedTxResponse{nil, err} } - err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { - // Create coincase tx - coincaseTx, err := w.createCoincase(dbtx, req.account, req.amount, req.lockTime) - if err != nil { - return err - } + // Reference server.go:236 + var coincaseAddr btcutil.Address + coincaseAddr, err = w.NewChangeAddress(req.account, waddrmgr.KeyScopeBIP0044) - NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) + coincaseTx, err := w.createCoincase(coincaseAddr, req.amount, req.lockTime) + if err != nil { + return createPostDatedTxResponse{nil, err} + } + + NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) + + // TODO : Continue to implementation - // TODO : Continue to implementation - return nil - }) if err != nil { return createPostDatedTxResponse{nil, err} } diff --git a/wallet/wallet.go b/wallet/wallet.go index 9a5f268fc0..bf9d4218c8 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -98,7 +98,9 @@ type Wallet struct { createTxRequests chan createTxRequest // Channel for transaction transfer requests - createTxTransferRequests chan createTxTransferRequest + createTxTransferRequests chan createTxTransferRequest + + // Channel for postdated transactions createPostDatedTxRequests chan createPostDatedTxRequest // Channels for the manager locker. @@ -142,10 +144,11 @@ func (w *Wallet) Start() { } w.quitMu.Unlock() - w.wg.Add(3) + w.wg.Add(4) go w.txCreator() go w.txTransferCreator() go w.walletLocker() + go w.postDatedTxCreator() } // SynchronizeRPC associates the wallet with the consensus RPC client, @@ -3438,27 +3441,28 @@ func Open(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks, log.Infof("Opened wallet") // TODO: log balance? last sync height? w := &Wallet{ - publicPassphrase: pubPass, - db: db, - Manager: addrMgr, - TxStore: txMgr, - lockedOutpoints: map[wire.OutPoint]struct{}{}, - recoveryWindow: recoveryWindow, - rescanAddJob: make(chan *RescanJob), - rescanBatch: make(chan *rescanBatch), - rescanNotifications: make(chan interface{}), - rescanProgress: make(chan *RescanProgressMsg), - rescanFinished: make(chan *RescanFinishedMsg), - createTxRequests: make(chan createTxRequest), - createTxTransferRequests: make(chan createTxTransferRequest), - unlockRequests: make(chan unlockRequest), - lockRequests: make(chan struct{}), - holdUnlockRequests: make(chan chan heldUnlock), - lockState: make(chan bool), - changePassphrase: make(chan changePassphraseRequest), - changePassphrases: make(chan changePassphrasesRequest), - chainParams: params, - quit: make(chan struct{}), + publicPassphrase: pubPass, + db: db, + Manager: addrMgr, + TxStore: txMgr, + lockedOutpoints: map[wire.OutPoint]struct{}{}, + recoveryWindow: recoveryWindow, + rescanAddJob: make(chan *RescanJob), + rescanBatch: make(chan *rescanBatch), + rescanNotifications: make(chan interface{}), + rescanProgress: make(chan *RescanProgressMsg), + rescanFinished: make(chan *RescanFinishedMsg), + createTxRequests: make(chan createTxRequest), + createTxTransferRequests: make(chan createTxTransferRequest), + createPostDatedTxRequests: make(chan createPostDatedTxRequest), + unlockRequests: make(chan unlockRequest), + lockRequests: make(chan struct{}), + holdUnlockRequests: make(chan chan heldUnlock), + lockState: make(chan bool), + changePassphrase: make(chan changePassphraseRequest), + changePassphrases: make(chan changePassphrasesRequest), + chainParams: params, + quit: make(chan struct{}), } w.NtfnServer = newNotificationServer(w) w.TxStore.NotifyUnspent = func(hash *chainhash.Hash, index uint32) { From 6707d414f2eac1766757229877a7e1f56fb6f96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Mon, 29 Oct 2018 23:54:22 +0300 Subject: [PATCH 23/25] wallet/coincasetx: Add coincase tx related implementations. --- wallet/coincasetx.go | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 wallet/coincasetx.go diff --git a/wallet/coincasetx.go b/wallet/coincasetx.go new file mode 100644 index 0000000000..0283d4d577 --- /dev/null +++ b/wallet/coincasetx.go @@ -0,0 +1,66 @@ +package wallet + +import ( + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" +) + +const ( + coincaseTxFlags = "/POSTDATED/" + + // TODO : Need to be in wire package + PostDatedTxVersion = 2 +) + +func createCoincaseScript() ([]byte, error) { + return txscript.NewScriptBuilder().AddData([]byte(coincaseTxFlags)).Script() +} + +// Reference : btcsuite\btcd\mining\mining.go:253 +func newCoincaseTransaction(pkScript []byte, amount int64, lockTime uint32) ( + *btcutil.Tx, error) { + var err error + + postDatedScript, err := createCoincaseScript() + if err != nil { + return nil, err + } + + tx := wire.NewMsgTx(PostDatedTxVersion) + + tx.AddTxIn(&wire.TxIn{ + // Coincase transactions have no inputs, so previous outpoint is + // zero hash and max index. + PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, + wire.MaxPrevOutIndex), + SignatureScript: postDatedScript, + Sequence: wire.MaxTxInSequenceNum, + }) + tx.AddTxOut(&wire.TxOut{ + Value: amount, + PkScript: pkScript, + }) + + tx.LockTime = lockTime + + // Reference : btcsuite\btcd\mining\mining.go:805 + // TODO : Check segwit related codes + + return btcutil.NewTx(tx), nil +} + +func (w *Wallet) createCoincase( + coincaseAddr btcutil.Address, + amount int64, lockTime uint32) (coincaseTx *btcutil.Tx, err error) { + + // Create coincase + pkScript, err := txscript.PayToAddrScript(coincaseAddr) + if err != nil { + return + } + + coincaseTx, err = newCoincaseTransaction(pkScript, amount, lockTime) + return +} From 4ba13dff8884b9cc55bd02e9bc00b465f38fba02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Tue, 30 Oct 2018 00:07:02 +0300 Subject: [PATCH 24/25] wallet/postdated: Fix&Notes&Refactor --- wallet/postdated.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/wallet/postdated.go b/wallet/postdated.go index 189b574d9c..c8307f4ca9 100644 --- a/wallet/postdated.go +++ b/wallet/postdated.go @@ -33,7 +33,7 @@ type ( resp chan createPostDatedTxResponse } createPostDatedTxResponse struct { - tx *txauthor.AuthoredTx + tx *AuthoredPostDatedTx err error } ) @@ -99,12 +99,10 @@ func NewUnsignedTransactionFromCoincase(coincaseTx *btcutil.Tx, output *wire.TxO // Get amount from coincase amount := btcutil.Amount(coincaseTx.MsgTx().TxOut[0].Value) - currentInputValues := make([]btcutil.Amount, 0, 1) - currentInputValues = append(currentInputValues, amount) + currentInputValues := []btcutil.Amount{amount} // Get pkScript from coincase - currentScripts := make([][]byte, 0, 1) - currentScripts = append(currentScripts, coincaseTx.MsgTx().TxOut[0].PkScript) + currentScripts := [][]byte{coincaseTx.MsgTx().TxOut[0].PkScript} return &AuthoredPostDatedTx{ Tx: unsignedTransaction, @@ -134,14 +132,18 @@ func (w *Wallet) createPostDatedTx(req createPostDatedTxRequest) createPostDated return createPostDatedTxResponse{nil, err} } - NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) - - // TODO : Continue to implementation - + var tx *AuthoredPostDatedTx + tx, err = NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) if err != nil { return createPostDatedTxResponse{nil, err} } + // TODO : Check createtx.go:156 + // tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs}) + + // TODO : validate mgsTx + + // TODO : Sign the tx // TODO : Continue to implementation return createPostDatedTxResponse{nil, nil} } From 1a27b8105a0b11abbda88867e0655b224f252ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Faruk=20Terzio=C4=9Flu?= Date: Thu, 1 Nov 2018 23:53:19 +0300 Subject: [PATCH 25/25] Use AuthoredTx instead of AuthoredPostDatedTx. Sign the created tx. Not tested yet. --- wallet/postdated.go | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/wallet/postdated.go b/wallet/postdated.go index c8307f4ca9..bcf333d031 100644 --- a/wallet/postdated.go +++ b/wallet/postdated.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txrules" + "github.com/btcsuite/btcwallet/walletdb" ) // wallet/wallet.go @@ -33,7 +34,7 @@ type ( resp chan createPostDatedTxResponse } createPostDatedTxResponse struct { - tx *AuthoredPostDatedTx + tx *txauthor.AuthoredTx err error } ) @@ -78,13 +79,7 @@ out: w.wg.Done() } -type AuthoredPostDatedTx struct { - Tx *wire.MsgTx - PrevScripts [][]byte - PrevInputValues []btcutil.Amount -} - -func NewUnsignedTransactionFromCoincase(coincaseTx *btcutil.Tx, output *wire.TxOut) (*AuthoredPostDatedTx, error) { +func NewUnsignedTransactionFromCoincase(coincaseTx *btcutil.Tx, output *wire.TxOut) (*txauthor.AuthoredTx, error) { // Create unsigned tx unsignedTransaction := &wire.MsgTx{ Version: PostDatedTxVersion, @@ -104,10 +99,12 @@ func NewUnsignedTransactionFromCoincase(coincaseTx *btcutil.Tx, output *wire.TxO // Get pkScript from coincase currentScripts := [][]byte{coincaseTx.MsgTx().TxOut[0].PkScript} - return &AuthoredPostDatedTx{ + return &txauthor.AuthoredTx{ Tx: unsignedTransaction, PrevScripts: currentScripts, PrevInputValues: currentInputValues, + TotalInput: 1, + ChangeIndex: -1, }, nil } @@ -132,18 +129,25 @@ func (w *Wallet) createPostDatedTx(req createPostDatedTxRequest) createPostDated return createPostDatedTxResponse{nil, err} } - var tx *AuthoredPostDatedTx - tx, err = NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) + var tx *txauthor.AuthoredTx + err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { + addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey) + + tx, err = NewUnsignedTransactionFromCoincase(coincaseTx, redeemOutput) + if err != nil { + return err + } + + return tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs}) + }) if err != nil { return createPostDatedTxResponse{nil, err} } - // TODO : Check createtx.go:156 - // tx.AddAllInputScripts(secretSource{w.Manager, addrmgrNs}) - - // TODO : validate mgsTx + err = validateMsgTx(tx.Tx, tx.PrevScripts, tx.PrevInputValues) + if err != nil { + return createPostDatedTxResponse{nil, err} + } - // TODO : Sign the tx - // TODO : Continue to implementation - return createPostDatedTxResponse{nil, nil} + return createPostDatedTxResponse{tx, nil} }