-
Notifications
You must be signed in to change notification settings - Fork 103
/
svm.go
232 lines (195 loc) · 7.46 KB
/
svm.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/**
* @file
* @copyright defined in go-seele/LICENSE
*/
package svm
import (
"math/big"
"github.com/seeleteam/go-seele/common"
"github.com/seeleteam/go-seele/common/errors"
"github.com/seeleteam/go-seele/contract/system"
"github.com/seeleteam/go-seele/core/state"
"github.com/seeleteam/go-seele/core/store"
"github.com/seeleteam/go-seele/core/svm/evm"
"github.com/seeleteam/go-seele/core/types"
"github.com/seeleteam/go-seele/core/vm"
)
// Context for other vm constructs
type Context struct {
Tx *types.Transaction
TxIndex int
Statedb *state.Statedb
BlockHeader *types.BlockHeader
BcStore store.BlockchainStore
}
// Process the tx
func Process(ctx *Context, height uint64) (*types.Receipt, error) {
// check the tx against the latest statedb, e.g. balance, nonce.
if err := ctx.Tx.ValidateState(ctx.Statedb, height); err != nil {
return nil, errors.NewStackedError(err, "failed to validate tx against statedb")
}
// Pay intrinsic gas all the time
gasLimit := ctx.Tx.Data.GasLimit
intrGas := ctx.Tx.IntrinsicGas()
if gasLimit < intrGas {
return nil, types.ErrIntrinsicGas
}
leftOverGas := gasLimit - intrGas
// init statedb and set snapshot
var err error
var receipt *types.Receipt
snapshot := ctx.Statedb.Prepare(ctx.TxIndex)
// create or execute contract
if contract := system.GetContractByAddress(ctx.Tx.Data.To); contract != nil { // system contract
receipt, err = processSystemContract(ctx, contract, snapshot, leftOverGas)
} else if ctx.Tx.IsCrossShardTx() && !ctx.Tx.Data.To.IsEVMContract() { // cross shard tx
return processCrossShardTransaction(ctx, snapshot)
} else { // evm
receipt, err = processEvmContract(ctx, leftOverGas)
}
// fmt.Println("svm.go-59, receipt.result", receipt.Result)
// account balance is not enough (account.balance < tx.amount)
if err == vm.ErrInsufficientBalance {
return nil, revertStatedb(ctx.Statedb, snapshot, err)
}
if err != nil {
if height <= common.SmartContractNonceForkHeight {
// fmt.Println("smart contract OLD logic")
ctx.Statedb.RevertToSnapshot(snapshot)
receipt.Failed = true
receipt.Result = []byte(err.Error())
} else {
// fmt.Println("smart contract NEW logic")
databaseAccountNonce := ctx.Statedb.GetNonce(ctx.Tx.Data.From)
setNonce := databaseAccountNonce
if ctx.Tx.Data.AccountNonce >= databaseAccountNonce {
setNonce = ctx.Tx.Data.AccountNonce + 1
}
ctx.Statedb.RevertToSnapshot(snapshot)
ctx.Statedb.SetNonce(ctx.Tx.Data.From, setNonce)
receipt.Failed = true
receipt.Result = []byte(err.Error())
}
}
// include the intrinsic gas
receipt.UsedGas += intrGas
// refund gas, capped to half of the used gas.
refund := ctx.Statedb.GetRefund()
if maxRefund := receipt.UsedGas / 2; refund > maxRefund {
refund = maxRefund
}
receipt.UsedGas -= refund
return handleFee(ctx, receipt, snapshot)
}
func processCrossShardTransaction(ctx *Context, snapshot int) (*types.Receipt, error) {
receipt := &types.Receipt{
TxHash: ctx.Tx.Hash,
UsedGas: types.CrossShardTotalGas,
}
// Add from nonce
ctx.Statedb.SetNonce(ctx.Tx.Data.From, ctx.Tx.Data.AccountNonce+1)
// Transfer amount
amount, sender := ctx.Tx.Data.Amount, ctx.Tx.Data.From
if ctx.Statedb.GetBalance(sender).Cmp(amount) < 0 {
return nil, revertStatedb(ctx.Statedb, snapshot, vm.ErrInsufficientBalance)
}
ctx.Statedb.SubBalance(sender, amount)
// check fee, only support non-contract tx.
txFee := new(big.Int).Mul(ctx.Tx.Data.GasPrice, new(big.Int).SetUint64(receipt.UsedGas))
if ctx.Statedb.GetBalance(sender).Cmp(txFee) < 0 {
return nil, revertStatedb(ctx.Statedb, snapshot, vm.ErrInsufficientBalance)
}
receipt.TotalFee = txFee.Uint64()
// handle fee
ctx.Statedb.SubBalance(sender, txFee)
minerFee := new(big.Int).Mul(ctx.Tx.Data.GasPrice, new(big.Int).SetUint64(types.CrossShardTransactionGas))
ctx.Statedb.AddBalance(ctx.BlockHeader.Creator, minerFee)
// Record statedb hash
var err error
if receipt.PostState, err = ctx.Statedb.Hash(); err != nil {
err = errors.NewStackedError(err, "failed to get statedb root hash")
return nil, revertStatedb(ctx.Statedb, snapshot, err)
}
return receipt, nil
}
func processSystemContract(ctx *Context, contract system.Contract, snapshot int, leftOverGas uint64) (*types.Receipt, error) {
// must execute to make sure that system contract address is available
if !ctx.Statedb.Exist(ctx.Tx.Data.To) {
ctx.Statedb.CreateAccount(ctx.Tx.Data.To)
}
var err error
receipt := &types.Receipt{
TxHash: ctx.Tx.Hash,
}
// Add from nonce
ctx.Statedb.SetNonce(ctx.Tx.Data.From, ctx.Tx.Data.AccountNonce+1)
// Transfer amount
amount, sender, recipient := ctx.Tx.Data.Amount, ctx.Tx.Data.From, ctx.Tx.Data.To
if ctx.Statedb.GetBalance(sender).Cmp(amount) < 0 {
return nil, revertStatedb(ctx.Statedb, snapshot, vm.ErrInsufficientBalance)
}
ctx.Statedb.SubBalance(sender, amount)
ctx.Statedb.AddBalance(recipient, amount)
// Check used gas is over flow
receipt.UsedGas = contract.RequiredGas(ctx.Tx.Data.Payload)
if receipt.UsedGas > leftOverGas {
return receipt, vm.ErrOutOfGas
}
// Run
receipt.Result, err = contract.Run(ctx.Tx.Data.Payload, system.NewContext(ctx.Tx, ctx.Statedb, ctx.BlockHeader))
return receipt, err
}
func processEvmContract(ctx *Context, gas uint64) (*types.Receipt, error) {
var err error
receipt := &types.Receipt{
TxHash: ctx.Tx.Hash,
}
statedb := &evm.StateDB{Statedb: ctx.Statedb}
e := evm.NewEVMByDefaultConfig(ctx.Tx, statedb, ctx.BlockHeader, ctx.BcStore)
caller := vm.AccountRef(ctx.Tx.Data.From)
var leftOverGas uint64
// fmt.Println("ctx.Tx.Data.To.IsEmpty()?", ctx.Tx.Data.To.IsEmpty())
if ctx.Tx.Data.To.IsEmpty() {
var createdContractAddr common.Address
// fmt.Println("processEvmContract.go-168:before create accountNonce ", ctx.Tx.Data.AccountNonce)
receipt.Result, createdContractAddr, leftOverGas, err = e.Create(caller, ctx.Tx.Data.Payload, gas, ctx.Tx.Data.Amount)
if !createdContractAddr.IsEmpty() {
receipt.ContractAddress = createdContractAddr.Bytes()
}
// fmt.Println("processEvmContract.go-173:after create accountNonce ", ctx.Tx.Data.AccountNonce)
} else {
ctx.Statedb.SetNonce(ctx.Tx.Data.From, ctx.Tx.Data.AccountNonce+1)
receipt.Result, leftOverGas, err = e.Call(caller, ctx.Tx.Data.To, ctx.Tx.Data.Payload, gas, ctx.Tx.Data.Amount)
}
receipt.UsedGas = gas - leftOverGas
// fmt.Println("svm.go-183 processEVMContract [after Create] err: ", err)
return receipt, err
}
func handleFee(ctx *Context, receipt *types.Receipt, snapshot int) (*types.Receipt, error) {
// Calculating the total fee
// For normal tx: fee = 20k * 1 Fan/gas = 0.0002 Seele
// For contract tx, average gas per tx is about 100k on ETH, fee = 100k * 1Fan/gas = 0.001 Seele
usedGas := new(big.Int).SetUint64(receipt.UsedGas)
totalFee := new(big.Int).Mul(usedGas, ctx.Tx.Data.GasPrice)
// Transfer fee to coinbase
// Note, the sender should always have enough balance.
ctx.Statedb.SubBalance(ctx.Tx.Data.From, totalFee)
ctx.Statedb.AddBalance(ctx.BlockHeader.Creator, totalFee)
receipt.TotalFee = totalFee.Uint64()
// Record statedb hash
var err error
if receipt.PostState, err = ctx.Statedb.Hash(); err != nil {
err = errors.NewStackedError(err, "failed to get statedb root hash")
return nil, revertStatedb(ctx.Statedb, snapshot, err)
}
// Add logs
receipt.Logs = ctx.Statedb.GetCurrentLogs()
if receipt.Logs == nil {
receipt.Logs = make([]*types.Log, 0)
}
return receipt, nil
}
func revertStatedb(statedb *state.Statedb, snapshot int, err error) error {
statedb.RevertToSnapshot(snapshot)
return err
}