Skip to content

Commit 3597d6b

Browse files
Merge pull request #27 from SiaFoundation/pj/fix-redistribute-wallet
Fix 'inUse' condition in wallet.Redistribute
2 parents 383f288 + 96a8bab commit 3597d6b

File tree

3 files changed

+207
-120
lines changed

3 files changed

+207
-120
lines changed

testutil/network.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,20 @@ func Network() (*consensus.Network, types.Block) {
2222
return n, genesisBlock
2323
}
2424

25-
// MineBlock mines a block with the given transactions.
25+
// MineBlock mines a block with the given transactions, transaction fees are
26+
// added to the miner payout.
2627
func MineBlock(cm *chain.Manager, minerAddress types.Address) types.Block {
28+
var minerFees types.Currency
29+
for _, txn := range cm.PoolTransactions() {
30+
minerFees = minerFees.Add(txn.TotalFees())
31+
}
32+
2733
state := cm.TipState()
2834
b := types.Block{
2935
ParentID: state.Index.ID,
3036
Timestamp: types.CurrentTimestamp(),
3137
Transactions: cm.PoolTransactions(),
32-
MinerPayouts: []types.SiacoinOutput{{Address: minerAddress, Value: state.BlockReward()}},
38+
MinerPayouts: []types.SiacoinOutput{{Address: minerAddress, Value: state.BlockReward().Add(minerFees)}},
3339
}
3440
for b.ID().CmpWork(state.ChildTarget) < 0 {
3541
b.Nonce += state.NonceFactor()

wallet/wallet.go

+28-14
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (sw *SingleAddressWallet) Balance() (balance Balance, err error) {
201201
balance.Immature = balance.Immature.Add(sco.SiacoinOutput.Value)
202202
} else {
203203
balance.Confirmed = balance.Confirmed.Add(sco.SiacoinOutput.Value)
204-
if time.Now().After(sw.locked[sco.ID]) && !tpoolSpent[sco.ID] {
204+
if !sw.isLocked(sco.ID) && !tpoolSpent[sco.ID] {
205205
balance.Spendable = balance.Spendable.Add(sco.SiacoinOutput.Value)
206206
}
207207
}
@@ -252,7 +252,7 @@ func (sw *SingleAddressWallet) SpendableOutputs() ([]SiacoinElement, error) {
252252
// filter outputs that are either locked, in the pool or have not yet matured
253253
unspent := utxos[:0]
254254
for _, sce := range utxos {
255-
if time.Now().Before(sw.locked[sce.ID]) || inPool[sce.ID] || bh < sce.MaturityHeight {
255+
if sw.isLocked(sce.ID) || inPool[sce.ID] || bh < sce.MaturityHeight {
256256
continue
257257
}
258258
unspent = append(unspent, sce)
@@ -297,8 +297,14 @@ func (sw *SingleAddressWallet) FundTransaction(txn *types.Transaction, amount ty
297297
// remove immature, locked and spent outputs
298298
cs := sw.cm.TipState()
299299
utxos := make([]types.SiacoinElement, 0, len(elements))
300+
var usedSum types.Currency
301+
var immatureSum types.Currency
300302
for _, sce := range elements {
301-
if time.Now().Before(sw.locked[sce.ID]) || tpoolSpent[sce.ID] || cs.Index.Height < sce.MaturityHeight {
303+
if used := sw.isLocked(sce.ID) || tpoolSpent[sce.ID]; used {
304+
usedSum = usedSum.Add(sce.SiacoinOutput.Value)
305+
continue
306+
} else if immature := cs.Index.Height < sce.MaturityHeight; immature {
307+
immatureSum = immatureSum.Add(sce.SiacoinOutput.Value)
302308
continue
303309
}
304310
utxos = append(utxos, sce.SiacoinElement)
@@ -310,20 +316,22 @@ func (sw *SingleAddressWallet) FundTransaction(txn *types.Transaction, amount ty
310316
})
311317

312318
var unconfirmedUTXOs []types.SiacoinElement
319+
var unconfirmedSum types.Currency
313320
if useUnconfirmed {
314321
for _, sce := range tpoolUtxos {
315-
if sce.SiacoinOutput.Address != sw.addr || time.Now().Before(sw.locked[sce.ID]) {
322+
if sce.SiacoinOutput.Address != sw.addr || sw.isLocked(sce.ID) {
316323
continue
317324
}
318325
unconfirmedUTXOs = append(unconfirmedUTXOs, sce)
326+
unconfirmedSum = unconfirmedSum.Add(sce.SiacoinOutput.Value)
319327
}
320-
321-
// sort by value, descending
322-
sort.Slice(unconfirmedUTXOs, func(i, j int) bool {
323-
return unconfirmedUTXOs[i].SiacoinOutput.Value.Cmp(unconfirmedUTXOs[j].SiacoinOutput.Value) > 0
324-
})
325328
}
326329

330+
// sort by value, descending
331+
sort.Slice(unconfirmedUTXOs, func(i, j int) bool {
332+
return unconfirmedUTXOs[i].SiacoinOutput.Value.Cmp(unconfirmedUTXOs[j].SiacoinOutput.Value) > 0
333+
})
334+
327335
// fund the transaction using the largest utxos first
328336
var selected []types.SiacoinElement
329337
var inputSum types.Currency
@@ -339,19 +347,19 @@ func (sw *SingleAddressWallet) FundTransaction(txn *types.Transaction, amount ty
339347
if inputSum.Cmp(amount) < 0 && useUnconfirmed {
340348
// try adding unconfirmed utxos.
341349
for _, sce := range unconfirmedUTXOs {
350+
selected = append(selected, sce)
351+
inputSum = inputSum.Add(sce.SiacoinOutput.Value)
342352
if inputSum.Cmp(amount) >= 0 {
343353
break
344354
}
345-
selected = append(selected, sce)
346-
inputSum = inputSum.Add(sce.SiacoinOutput.Value)
347355
}
348356

349357
if inputSum.Cmp(amount) < 0 {
350358
// still not enough funds
351-
return nil, ErrNotEnoughFunds
359+
return nil, fmt.Errorf("%w: inputs %v < needed %v (used: %v immature: %v unconfirmed: %v)", ErrNotEnoughFunds, inputSum.String(), amount.String(), usedSum.String(), immatureSum.String(), unconfirmedSum.String())
352360
}
353361
} else if inputSum.Cmp(amount) < 0 {
354-
return nil, ErrNotEnoughFunds
362+
return nil, fmt.Errorf("%w: inputs %v < needed %v (used: %v immature: %v", ErrNotEnoughFunds, inputSum.String(), amount.String(), usedSum.String(), immatureSum.String())
355363
}
356364

357365
// check if remaining utxos should be defragged
@@ -496,7 +504,7 @@ func (sw *SingleAddressWallet) Redistribute(outputs int, amount, feePerByte type
496504
// unused, matured and has the same value
497505
utxos := make([]types.SiacoinElement, 0, len(elements))
498506
for _, sce := range elements {
499-
inUse := time.Now().After(sw.locked[sce.ID]) || inPool[sce.ID]
507+
inUse := sw.isLocked(sce.ID) || inPool[sce.ID]
500508
matured := bh >= sce.MaturityHeight
501509
sameValue := sce.SiacoinOutput.Value.Equals(amount)
502510

@@ -602,6 +610,12 @@ func (sw *SingleAddressWallet) ReleaseInputs(txns ...types.Transaction) {
602610
}
603611
}
604612

613+
// isLocked returns true if the siacoin output with given id is locked, this
614+
// method must be called whilst holding the mutex lock.
615+
func (sw *SingleAddressWallet) isLocked(id types.Hash256) bool {
616+
return time.Now().Before(sw.locked[id])
617+
}
618+
605619
// IsRelevantTransaction returns true if the v1 transaction is relevant to the
606620
// address
607621
func IsRelevantTransaction(txn types.Transaction, addr types.Address) bool {

0 commit comments

Comments
 (0)