-
Notifications
You must be signed in to change notification settings - Fork 0
/
tx_withdraw_stake.go
142 lines (118 loc) · 4.79 KB
/
tx_withdraw_stake.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package execution
import (
"fmt"
"math/big"
"github.com/dnerochain/dnero/common"
"github.com/dnerochain/dnero/common/result"
"github.com/dnerochain/dnero/core"
st "github.com/dnerochain/dnero/ledger/state"
"github.com/dnerochain/dnero/ledger/types"
)
var _ TxExecutor = (*WithdrawStakeExecutor)(nil)
// ------------------------------- WithdrawStake Transaction -----------------------------------
// WithdrawStakeExecutor implements the TxExecutor interface
type WithdrawStakeExecutor struct {
state *st.LedgerState
}
// NewWithdrawStakeExecutor creates a new instance of WithdrawStakeExecutor
func NewWithdrawStakeExecutor(state *st.LedgerState) *WithdrawStakeExecutor {
return &WithdrawStakeExecutor{
state: state,
}
}
func (exec *WithdrawStakeExecutor) sanityCheck(chainID string, view *st.StoreView, transaction types.Tx) result.Result {
tx := transaction.(*types.WithdrawStakeTx)
res := tx.Source.ValidateBasic()
if res.IsError() {
return res
}
sourceAccount, success := getInput(view, tx.Source)
if success.IsError() {
return result.Error("Failed to get the source account: %v", tx.Source.Address)
}
signBytes := tx.SignBytes(chainID)
res = validateInputAdvanced(sourceAccount, signBytes, tx.Source)
if res.IsError() {
logger.Debugf(fmt.Sprintf("validateSourceAdvanced failed on %v: %v", tx.Source.Address.Hex(), res))
return res
}
blockHeight := view.Height() + 1 // the view points to the parent of the current block
if minTxFee, success := sanityCheckForFee(tx.Fee, blockHeight); !success {
return result.Error("Insufficient fee. Transaction fee needs to be at least %v DTokenWei",
minTxFee).WithErrorCode(result.CodeInvalidFee)
}
if !(tx.Purpose == core.StakeForValidator || tx.Purpose == core.StakeForSentry) {
return result.Error("Invalid stake purpose!").
WithErrorCode(result.CodeInvalidStakePurpose)
}
minimalBalance := tx.Fee
if !sourceAccount.Balance.IsGTE(minimalBalance) {
logger.Infof(fmt.Sprintf("WithdrawStake: Source did not have enough balance %v", tx.Source.Address.Hex()))
return result.Error("WithdrawStake: Source balance is %v, but required minimal balance is %v",
sourceAccount.Balance, minimalBalance)
}
return result.OK
}
// NOTE: WithdrawStakeExecutor.process() does NOT return the stake to the source. Instead, it updates
// the ReturnHeight of the withdrawn stake. The stake will be returned to the source when
// the block height reaches the ReturnHeigth
func (exec *WithdrawStakeExecutor) process(chainID string, view *st.StoreView, transaction types.Tx) (common.Hash, result.Result) {
tx := transaction.(*types.WithdrawStakeTx)
sourceAccount, success := getInput(view, tx.Source)
if success.IsError() {
return common.Hash{}, result.Error("Failed to get the source account")
}
if !chargeFee(sourceAccount, tx.Fee) {
return common.Hash{}, result.Error("Failed to charge transaction fee")
}
sourceAddress := tx.Source.Address
holderAddress := tx.Holder.Address
if tx.Purpose == core.StakeForValidator {
vcp := view.GetValidatorCandidatePool()
currentHeight := exec.state.Height()
err := vcp.WithdrawStake(sourceAddress, holderAddress, currentHeight)
if err != nil {
return common.Hash{}, result.Error("Failed to withdraw stake, err: %v", err)
}
view.UpdateValidatorCandidatePool(vcp)
} else if tx.Purpose == core.StakeForSentry {
gcp := view.GetSentryCandidatePool()
currentHeight := exec.state.Height()
err := gcp.WithdrawStake(sourceAddress, holderAddress, currentHeight)
if err != nil {
return common.Hash{}, result.Error("Failed to withdraw stake, err: %v", err)
}
view.UpdateSentryCandidatePool(gcp)
} else {
return common.Hash{}, result.Error("Invalid staking purpose").WithErrorCode(result.CodeInvalidStakePurpose)
}
// Only update stake transaction height list for validator stake tx.
if tx.Purpose == core.StakeForValidator {
hl := view.GetStakeTransactionHeightList()
if hl == nil {
hl = &types.HeightList{}
}
blockHeight := view.Height() + 1 // the view points to the parent of the current block
hl.Append(blockHeight)
view.UpdateStakeTransactionHeightList(hl)
}
sourceAccount.Sequence++
view.SetAccount(sourceAddress, sourceAccount)
txHash := types.TxID(chainID, tx)
return txHash, result.OK
}
func (exec *WithdrawStakeExecutor) getTxInfo(transaction types.Tx) *core.TxInfo {
tx := transaction.(*types.WithdrawStakeTx)
return &core.TxInfo{
Address: tx.Source.Address,
Sequence: tx.Source.Sequence,
EffectiveGasPrice: exec.calculateEffectiveGasPrice(transaction),
}
}
func (exec *WithdrawStakeExecutor) calculateEffectiveGasPrice(transaction types.Tx) *big.Int {
tx := transaction.(*types.WithdrawStakeTx)
fee := tx.Fee
gas := new(big.Int).SetUint64(getRegularTxGas(exec.state))
effectiveGasPrice := new(big.Int).Div(fee.DTokenWei, gas)
return effectiveGasPrice
}