Skip to content

Commit

Permalink
send remainder to the last used private key
Browse files Browse the repository at this point in the history
  • Loading branch information
glossd committed Dec 28, 2021
1 parent 4dab3da commit 0744355
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 58 deletions.
5 changes: 3 additions & 2 deletions addressinfo/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package addressinfo
import (
"github.com/btcsuite/btcd/wire"
"github.com/glossd/btc/netchain"
mathrand "math/rand"
)

const MockAddressBalance int64 = 1e6

func FetchMock(address string, net netchain.Net) (Address, error) {
var utxoMock = UTXO{
TxID: wire.NewMsgTx(wire.TxVersion).TxHash().String(),
Balance: mathrand.Int63n(50000000) + 1000000,
Balance: MockAddressBalance,
Pbscript: "76a914fee7132bbe9201c4f1a0f846b5f714d9335e263088ac",
TxOutIdx: 1,
}
Expand Down
30 changes: 15 additions & 15 deletions txutil/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type CreateParams struct {
// WIF-format. Will be omitted if PrivateKeys are specified.
PrivateKey string
// Iteratively includes each key in transaction until the full amount can be transferred.
// The remainder of bitcoins will be sent to the first private key.
// If the last used private key had some satoshi left, that remainder will be sent to that last private key.
PrivateKeys []string
// Bitcoin address of the receiver. Amount or SendAll must be set. Will be omitted if Destinations are specified.
Destination string
Expand Down Expand Up @@ -108,13 +108,13 @@ func checkCreateParams(p CreateParams) (CreateParams, error) {
if p.Amount < minSatoshiToSend && !p.SendAll {
return CreateParams{}, fmt.Errorf("amount of satoshi can't be less than %d", minSatoshiToSend)
}
payAddress, err := toPayAddress(p.Destination, p.Net)
payAddress, err := addressToPkScript(p.Destination, p.Net)
if err != nil {
return CreateParams{}, err
}
p.destInfos = []destinationInfo{{
Destination: Destination{Address: p.Destination, Amount: p.Amount},
payAddress: payAddress,
pkScript: payAddress,
}}
} else {
if len(p.Destinations) > 1 && p.SendAll {
Expand Down Expand Up @@ -219,48 +219,48 @@ func addInputs(tx *wire.MsgTx, utxos []addressinfo.UTXO) error {
func addTxOutputs(tx *wire.MsgTx, params CreateParams, satoshiRemainder int64, addrs []address) {
if params.SendAll {
fullBalance := calcBalanceOfAddresses(addrs)
tx.AddTxOut(wire.NewTxOut(fullBalance-params.MinerFee, params.destInfos[0].payAddress))
tx.AddTxOut(wire.NewTxOut(fullBalance-params.MinerFee, params.destInfos[0].pkScript))
} else {
for _, info := range params.destInfos {
tx.AddTxOut(wire.NewTxOut(info.Amount, info.payAddress))
tx.AddTxOut(wire.NewTxOut(info.Amount, info.pkScript))
}
if satoshiRemainder > 0 {
tx.AddTxOut(wire.NewTxOut(satoshiRemainder, params.pkInfos[0].payAddress))
tx.AddTxOut(wire.NewTxOut(satoshiRemainder, params.pkInfos[len(params.pkInfos)-1].pkScript))
}
}
}

type privateKeyInfo struct {
key string
address string
payAddress []byte
key string
address string
pkScript []byte
}

func toPkInfo(privKey string, net netchain.Net) (privateKeyInfo, error) {
addr, err := wallet.AddressFromPrivateKey(privKey, net)
if err != nil {
return privateKeyInfo{}, err
}
payAddress, err := toPayAddress(addr, net)
pkScript, err := addressToPkScript(addr, net)
if err != nil {
return privateKeyInfo{}, err
}
return privateKeyInfo{key: privKey, address: addr, payAddress: payAddress}, nil
return privateKeyInfo{key: privKey, address: addr, pkScript: pkScript}, nil
}

type destinationInfo struct {
Destination
payAddress []byte
pkScript []byte
}

func toDestInfo(d Destination, net netchain.Net) (destinationInfo, error) {
payAddress, err := toPayAddress(d.Address, net)
pkScript, err := addressToPkScript(d.Address, net)
if err != nil {
return destinationInfo{}, err
}
return destinationInfo{
Destination: d,
payAddress: payAddress,
pkScript: pkScript,
}, err
}

Expand All @@ -287,7 +287,7 @@ func chooseUTXOs(utxos []addressinfo.UTXO, amountToSend int64) (toSpend []addres
panic("address doesn't have enough bitcoins for transfer")
}

func toPayAddress(address string, net netchain.Net) ([]byte, error) {
func addressToPkScript(address string, net netchain.Net) ([]byte, error) {
// extracting address as []byte from function argument
destinationAddr, err := btcutil.DecodeAddress(address, net.GetBtcdNetParams())
if err != nil {
Expand Down
118 changes: 77 additions & 41 deletions txutil/create_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package txutil

import (
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/glossd/btc/addressinfo"
"github.com/glossd/btc/netchain"
"github.com/stretchr/testify/assert"
Expand All @@ -10,69 +10,90 @@ import (

const privateKey1 = "932u6Q4xEC9UYRb3rS2BWrSpSPEt5KaU8NNP7EWy7zSkWmfBiGe"
const privateKey2 = "cMvRbsVJKjRkZTV7tosWEYEu1x8tQcnLEbC64RiKwPeeEz29j8QZ"
const privateKey3 = "93UVjiGYyB6q16iMPuKjYePdLesaYvdMyP3EjE1PjZEqzd456h1"
// destination of each private key
const destination1 = "mgFv6afUVhrdd3D6mY2iyWzHVk5b64qTok"
const destination2 = "n4kkk9H2jGj7t8LA4vxK4DHM7Lq95VaEXC"
const destination3 = "mwRL1TpsRSFy5KXbxEd2KrHiD16VvbbAdj"

func TestCreate(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKey: privateKey1,
Destination: destination2,
Amount: 500000,
Net: netchain.TestNet,
})
assert.Nil(t, err)

tx, err := hexDecodeTx(rawTx)
assert.Nil(t, err)
assert.Positive(t, len(tx.TxIn))
assert.EqualValues(t, 1, len(tx.TxOut))

fmt.Println("raw signed transaction is: ", rawTx)
}

func TestCreate_SendAll(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKey: privateKey1,
Destination: destination2,
SendAll: true,
Net: netchain.TestNet,
})
assert.Nil(t, err)
t.Run("Through amount", func(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKey: privateKey1,
Destination: destination2,
Amount: addressinfo.MockAddressBalance - defaultMinerFee,
Fetch: addressinfo.FetchMock,
Net: netchain.TestNet,
})
assert.Nil(t, err)

tx, err := hexDecodeTx(rawTx)
assert.Nil(t, err)
assert.Positive(t, len(tx.TxIn))
tx := decodeTx(t, rawTx)
assert.EqualValues(t, 1, len(tx.TxIn))
assert.EqualValues(t, 1, len(tx.TxOut))
})
t.Run("SendAll flag true", func(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKey: privateKey1,
Destination: destination2,
SendAll: true,
Fetch: addressinfo.FetchMock,
Net: netchain.TestNet,
})
assert.Nil(t, err)

fmt.Println("raw signed transaction is: ", rawTx)
tx := decodeTx(t, rawTx)
assert.EqualValues(t, 1, len(tx.TxIn))
assert.EqualValues(t, 1, len(tx.TxOut))
})
}

func TestCreate_MultiplePrivateKeys(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKeys: []string{privateKey1, privateKey2},
Destination: destination3,
SendAll: true,
Net: netchain.TestNet,
t.Run("SendAll", func(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKeys: []string{privateKey1, privateKey2},
Destination: destination3,
SendAll: true,
Fetch: addressinfo.FetchMock,
Net: netchain.TestNet,
})
assert.Nil(t, err)

tx := decodeTx(t, rawTx)
assert.GreaterOrEqual(t, len(tx.TxIn), 2)
})
assert.Nil(t, err)
t.Run("WithRemainder", func(t *testing.T) {
amount := addressinfo.MockAddressBalance*3/2 - defaultMinerFee
rawTx, err := Create(CreateParams{
PrivateKeys: []string{privateKey1, privateKey2},
Destination: destination3,
Amount: amount,
Fetch: addressinfo.FetchMock,
Net: netchain.TestNet,
})
assert.Nil(t, err)

tx, err := hexDecodeTx(rawTx)
assert.Nil(t, err)
assert.GreaterOrEqual(t, len(tx.TxIn), 2)
fmt.Println("raw signed transaction is: ", rawTx)
tx := decodeTx(t, rawTx)
assert.EqualValues(t, 2, len(tx.TxIn))
assert.EqualValues(t, 2, len(tx.TxOut))
assert.EqualValues(t, amount, tx.TxOut[0].Value)
assert.EqualValues(t, addressinfo.MockAddressBalance/2, tx.TxOut[1].Value)
assert.EqualValues(t, tx.TxOut[1].PkScript, addressPkScript(t, destination2))
})
}


func TestCreate_ToMultipleDestinations(t *testing.T) {
rawTx, err := Create(CreateParams{
PrivateKey: privateKey1,
Destinations: []Destination{{Address: destination2, Amount: 200000}, {Address: destination3, Amount: 300000}},
Fetch: addressinfo.FetchMock,
Net: netchain.TestNet,
})
assert.Nil(t, err)

tx, err := hexDecodeTx(rawTx)
assert.Nil(t, err)
tx := decodeTx(t, rawTx)
assert.EqualValues(t, 3, len(tx.TxOut))
fmt.Println("raw signed transaction is: ", rawTx)
}

func TestCreate_Validation(t *testing.T) {
Expand All @@ -99,6 +120,7 @@ func TestCreate_Validation(t *testing.T) {

for _, test := range shouldntPass {
test.input.Net = netchain.TestNet
test.input.Fetch = addressinfo.FetchMock
_, err := Create(test.input)
assert.NotNil(t, err)
}
Expand All @@ -118,3 +140,17 @@ func TestCreate_Validation(t *testing.T) {
assert.Nil(t, err)
}
}

func decodeTx(t *testing.T, rawTx string) *wire.MsgTx {
tx, err := hexDecodeTx(rawTx)
assert.Nil(t, err)
return tx
}

func addressPkScript(t *testing.T, address string) []byte {
script, err := addressToPkScript(address, netchain.TestNet)
assert.Nil(t, err)
return script
}


0 comments on commit 0744355

Please sign in to comment.