forked from CyberMiles/travis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathethereum.go
344 lines (284 loc) · 11 KB
/
ethereum.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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package app
import (
"encoding/json"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
abciTypes "github.com/tendermint/tendermint/abci/types"
tmLog "github.com/tendermint/tendermint/libs/log"
"github.com/CyberMiles/travis/api"
"github.com/CyberMiles/travis/errors"
"github.com/CyberMiles/travis/utils"
emtTypes "github.com/CyberMiles/travis/vm/types"
)
type FromTo struct {
from common.Address
to common.Address
}
// EthermintApplication implements an ABCI application
// #stable - 0.4.0
type EthermintApplication struct {
// backend handles the ethereum state machine
// and wrangles other services started by an ethereum node (eg. tx pool)
backend *api.Backend // backend ethereum struct
checkTxState *state.StateDB
// an ethereum rpc client we can forward queries to
rpcClient *rpc.Client
// strategy for validator compensation
strategy *emtTypes.Strategy
logger tmLog.Logger
lowPriceCheckTransactions map[FromTo]struct{}
lowPriceDeliverTransactions map[FromTo]struct{}
}
// NewEthermintApplication creates a fully initialised instance of EthermintApplication
// #stable - 0.4.0
func NewEthermintApplication(backend *api.Backend,
client *rpc.Client, strategy *emtTypes.Strategy) (*EthermintApplication, error) {
state := backend.ManagedState()
if state == nil {
panic("Error getting latest state")
}
app := &EthermintApplication{
backend: backend,
rpcClient: client,
checkTxState: state.StateDB,
strategy: strategy,
lowPriceCheckTransactions: make(map[FromTo]struct{}),
lowPriceDeliverTransactions: make(map[FromTo]struct{}),
}
if err := app.backend.InitEthState(app.Receiver()); err != nil {
return nil, err
}
return app, nil
}
// SetLogger sets the logger for the ethermint application
// #unstable
func (app *EthermintApplication) SetLogger(log tmLog.Logger) {
app.logger = log
}
var bigZero = big.NewInt(0)
// maxTransactionSize is 32KB in order to prevent DOS attacks
const maxTransactionSize = 32768
// Info returns information about the last height and app_hash to the tendermint engine
// #stable - 0.4.0
func (app *EthermintApplication) Info(req abciTypes.RequestInfo) abciTypes.ResponseInfo {
blockchain := app.backend.Ethereum().BlockChain()
currentBlock := blockchain.CurrentBlock()
height := currentBlock.Number()
hash := currentBlock.Hash()
app.logger.Debug("Info", "height", height) // nolint: errcheck
// This check determines whether it is the first time ethermint gets started.
// If it is the first time, then we have to respond with an empty hash, since
// that is what tendermint expects.
if height.Cmp(bigZero) == 0 {
return abciTypes.ResponseInfo{
Data: "ABCIEthereum",
LastBlockHeight: height.Int64(),
LastBlockAppHash: []byte{},
}
}
return abciTypes.ResponseInfo{
Data: "ABCIEthereum",
LastBlockHeight: height.Int64(),
LastBlockAppHash: hash[:],
}
}
// SetOption sets a configuration option
// #stable - 0.4.0
func (app *EthermintApplication) SetOption(req abciTypes.RequestSetOption) abciTypes.ResponseSetOption {
app.logger.Debug("SetOption", "key", req.GetKey(), "value", req.GetValue()) // nolint: errcheck
return abciTypes.ResponseSetOption{}
}
// InitChain initializes the validator set
// #stable - 0.4.0
func (app *EthermintApplication) InitChain(req abciTypes.RequestInitChain) abciTypes.ResponseInitChain {
app.logger.Debug("InitChain") // nolint: errcheck
app.SetValidators(req.GetValidators())
return abciTypes.ResponseInitChain{}
}
// CheckTx checks a transaction is valid but does not mutate the state
// #stable - 0.4.0
func (app *EthermintApplication) CheckTx(tx *ethTypes.Transaction) abciTypes.ResponseCheckTx {
app.logger.Debug("CheckTx: Received valid transaction", "tx", tx) // nolint: errcheck
return app.validateTx(tx)
}
// DeliverTx executes a transaction against the latest state
// #stable - 0.4.0
func (app *EthermintApplication) DeliverTx(tx *ethTypes.Transaction) abciTypes.ResponseDeliverTx {
app.logger.Debug("DeliverTx: Received valid transaction", "tx", tx) // nolint: errcheck
networkId := big.NewInt(int64(app.backend.Ethereum().NetVersion()))
signer := ethTypes.NewEIP155Signer(networkId)
from, err := ethTypes.Sender(signer, tx)
if err != nil {
// TODO: Add errors.CodeTypeInvalidSignature ?
return abciTypes.ResponseDeliverTx{
Code: errors.CodeTypeInternalErr,
Log: err.Error()}
}
if code, errLog := app.lowPriceTxCheck(from, tx, app.lowPriceDeliverTransactions); code != abciTypes.CodeTypeOK {
return abciTypes.ResponseDeliverTx{Code: code, Log: errLog}
}
res := app.backend.DeliverTx(tx)
if res.IsErr() {
// nolint: errcheck
app.logger.Error("DeliverTx: Error delivering tx to ethereum backend", "tx", tx,
"err", res.String())
return res
}
app.CollectTx(tx)
return abciTypes.ResponseDeliverTx{
Code: abciTypes.CodeTypeOK,
}
}
func (app *EthermintApplication) DeliverTxState() *state.StateDB {
return app.backend.DeliverTxState()
}
// BeginBlock starts a new Ethereum block
// #stable - 0.4.0
func (app *EthermintApplication) BeginBlock(beginBlock abciTypes.RequestBeginBlock) abciTypes.ResponseBeginBlock {
app.logger.Debug("BeginBlock") // nolint: errcheck
// update the eth header with the tendermint header
app.backend.UpdateHeaderWithTimeInfo(beginBlock.GetHeader(), beginBlock.GetHash())
return abciTypes.ResponseBeginBlock{}
}
// EndBlock accumulates rewards for the validators and updates them
// #stable - 0.4.0
func (app *EthermintApplication) EndBlock(endBlock abciTypes.RequestEndBlock) abciTypes.ResponseEndBlock {
app.logger.Debug("EndBlock", "height", endBlock.GetHeight()) // nolint: errcheck
app.backend.AccumulateRewards(app.backend.Ethereum().BlockChain().Config(), app.strategy)
app.backend.EndBlock()
return app.GetUpdatedValidators()
}
// Commit commits the block and returns a hash of the current state
// #stable - 0.4.0
func (app *EthermintApplication) Commit() (abciTypes.ResponseCommit, error) {
app.logger.Debug("Commit") // nolint: errcheck
blockHash, err := app.backend.Commit(app.Receiver())
if err != nil {
// nolint: errcheck
app.logger.Error("Error getting latest ethereum state", "err", err)
return abciTypes.ResponseCommit{
Data: blockHash[:],
}, err
}
state, err := app.backend.ResetState()
if err != nil {
app.logger.Error("Error getting latest state", "err", err) // nolint: errcheck
return abciTypes.ResponseCommit{
Data: blockHash[:],
}, err
}
app.checkTxState = state.StateDB
app.lowPriceCheckTransactions = make(map[FromTo]struct{})
app.lowPriceDeliverTransactions = make(map[FromTo]struct{})
return abciTypes.ResponseCommit{
Data: blockHash[:],
}, nil
}
// Query queries the state of the EthermintApplication
// #stable - 0.4.0
func (app *EthermintApplication) Query(query abciTypes.RequestQuery) abciTypes.ResponseQuery {
app.logger.Debug("Query") // nolint: errcheck
var in jsonRequest
if err := json.Unmarshal(query.Data, &in); err != nil {
return abciTypes.ResponseQuery{Code: errors.CodeTypeInternalErr,
Log: err.Error()}
}
var result interface{}
if err := app.rpcClient.Call(&result, in.Method, in.Params...); err != nil {
return abciTypes.ResponseQuery{Code: errors.CodeTypeInternalErr,
Log: err.Error()}
}
bytes, err := json.Marshal(result)
if err != nil {
return abciTypes.ResponseQuery{Code: errors.CodeTypeInternalErr,
Log: err.Error()}
}
return abciTypes.ResponseQuery{Code: abciTypes.CodeTypeOK, Value: bytes}
}
//-------------------------------------------------------
// validateTx checks the validity of a tx against the blockchain's current state.
// it duplicates the logic in ethereum's tx_pool
func (app *EthermintApplication) validateTx(tx *ethTypes.Transaction) abciTypes.ResponseCheckTx {
currentState, from, nonce, resp := app.basicCheck(tx)
if resp.Code != abciTypes.CodeTypeOK {
return resp
}
intrGas, err := core.IntrinsicGas(tx.Data(), tx.To() == nil, true) // homestead == true
if err != nil {
return abciTypes.ResponseCheckTx{
Code: errors.CodeTypeBaseInvalidInput,
Log: err.Error()}
}
if tx.Gas() < intrGas {
return abciTypes.ResponseCheckTx{
Code: errors.CodeTypeBaseInvalidInput,
Log: core.ErrIntrinsicGas.Error()}
}
defaultCost := new(big.Int).Mul(new(big.Int).SetUint64(utils.GetParams().GasPrice), new(big.Int).SetUint64(tx.Gas()))
// Transactor should have enough funds to cover the costs
currentBalance := currentState.GetBalance(from)
// This check don't do anything
// It only filter the tx which qualified the freegas requirement
if tx.GasPrice().Int64() == 0 && tx.Gas() > utils.GetParams().LowPriceTxGasLimit &&
tx.To() != nil && len(tx.Data()) > 0 {
if currentState.GetBalance(*tx.To()).Cmp(defaultCost) < 0 {
return abciTypes.ResponseCheckTx{
// TODO: Add errors.CodeTypeInsufficientFunds ?
Code: errors.CodeHighGasLimitErr,
Log: "The gas limit is too high for low price transaction",
}
}
} else {
// cost == V + GP * GL
if currentBalance.Cmp(tx.Cost()) < 0 {
return abciTypes.ResponseCheckTx{
// TODO: Add errors.CodeTypeInsufficientFunds ?
Code: errors.CodeTypeBaseInvalidInput,
Log: fmt.Sprintf(
"Current balance: %s, tx cost: %s",
currentBalance, tx.Cost())}
}
if code, errLog := app.lowPriceTxCheck(from, tx, app.lowPriceCheckTransactions); code != abciTypes.CodeTypeOK {
return abciTypes.ResponseCheckTx{Code: code, Log: errLog}
}
}
// Update ether balances
// amount + gasprice * gaslimit
currentState.SubBalance(from, tx.Cost())
// tx.To() returns a pointer to a common address. It returns nil
// if it is a contract creation transaction.
if to := tx.To(); to != nil {
currentState.AddBalance(*to, tx.Value())
}
currentState.SetNonce(from, nonce+1)
return abciTypes.ResponseCheckTx{Code: abciTypes.CodeTypeOK}
}
func (app *EthermintApplication) lowPriceTxCheck(from common.Address, tx *ethTypes.Transaction, lowPriceTxs map[FromTo]struct{}) (uint32, string) {
// Iterate over all transactions to check if the gas price is too low for the
// non-first transaction with the same from/to address
// Todo performance maybe
var to common.Address
if tx.To() != nil {
to = *tx.To()
}
ft := FromTo{from: from, to: to}
if tx.GasPrice().Cmp(new(big.Int).SetUint64(utils.GetParams().GasPrice)) < 0 {
if _, ok := lowPriceTxs[ft]; ok {
return errors.CodeLowGasPriceErr, "The gas price is too low for transaction"
}
// Bypass if the gasprice == 0 and gaslimit > lowPriceCap
if (tx.GasPrice().Int64() > 0 || tx.To() == nil) && tx.Gas() > utils.GetParams().LowPriceTxGasLimit {
return errors.CodeHighGasLimitErr, "The gas limit is too high for low price transaction"
}
if len(lowPriceTxs) > utils.GetParams().LowPriceTxSlotsCap {
return errors.CodeLowPriceTxCapErr, "The capacity of one block is reached for low price transactions"
}
lowPriceTxs[ft] = struct{}{}
}
return abciTypes.CodeTypeOK, ""
}