Newer
Older
// Copyright 2015, 2016 Eris Industries (UK) Ltd.
// This file is part of Eris-RT
// Eris-RT is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Eris-RT is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Eris-RT. If not, see <http://www.gnu.org/licenses/>.
import (
"bytes"
"encoding/hex"
"fmt"
"sync"
events "github.com/tendermint/go-events"
client "github.com/tendermint/go-rpc/client"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmsp "github.com/tendermint/tmsp/types"
manager_types "github.com/eris-ltd/eris-db/manager/types"
sm "github.com/eris-ltd/eris-db/manager/eris-mint/state"
types "github.com/eris-ltd/eris-db/txs"
)
//--------------------------------------------------------------------------------
// ErisMint holds the current state, runs transactions, computes hashes.
// Typically two connections are opened by the tendermint core:
// one for mempool, one for consensus.
state *sm.State
cache *sm.BlockCache
checkCache *sm.BlockCache // for CheckTx (eg. so we get nonces right)
evc *events.EventCache
evsw *events.EventSwitch
// client to the tendermint core rpc
client *client.ClientURI
host string // tendermint core endpoint
// NOTE [ben] Compiler check to ensure ErisMint successfully implements
// eris-db/manager/types.Application
var _ manager_types.Application = (*ErisMint)(nil)
// NOTE: [ben] also automatically implements tmsp.Application,
// undesired but unharmful
// var _ tmsp.Application = (*ErisMint)(nil)
func (app *ErisMint) GetState() *sm.State {
app.mtx.Lock()
defer app.mtx.Unlock()
return app.state.Copy()
}
// TODO: this is used for call/callcode and to get nonces during mempool.
// the former should work on last committed state only and the later should
// be handled by the client, or a separate wallet-like nonce tracker thats not part of the app
func (app *ErisMint) GetCheckCache() *sm.BlockCache {
func (app *ErisMint) SetHostAddress(host string) {
app.host = host
app.client = client.NewClientURI(host) //fmt.Sprintf("http://%s", host))
}
// Broadcast a tx to the tendermint core
// NOTE: this assumes we know the address of core
func (app *ErisMint) BroadcastTx(tx types.Tx) error {
buf := new(bytes.Buffer)
var n int
var err error
wire.WriteBinary(struct{ types.Tx }{tx}, buf, &n, &err)
if err != nil {
return err
}
params := map[string]interface{}{
"tx": hex.EncodeToString(buf.Bytes()),
}
var result ctypes.TMResult
_, err = app.client.Call("broadcast_tx_sync", params, &result)
return err
func NewErisMint(s *sm.State, evsw *events.EventSwitch) *ErisMint {
return &ErisMint{
state: s,
cache: sm.NewBlockCache(s),
checkCache: sm.NewBlockCache(s),
evc: events.NewEventCache(evsw),
evsw: evsw,
}
// Implements manager/types.Application
func (app *ErisMint) Info() (info string) {
// Implements manager/types.Application
func (app *ErisMint) SetOption(key string, value string) (log string) {
// Implements manager/types.Application
func (app *ErisMint) AppendTx(txBytes []byte) (res tmsp.Result) {
app.nTxs += 1
// XXX: if we had tx ids we could cache the decoded txs on CheckTx
var n int
var err error
tx := new(types.Tx)
buf := bytes.NewBuffer(txBytes)
wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err)
if err != nil {
return tmsp.NewError(tmsp.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err))
}
log.Info("AppendTx", "tx", *tx)
err = sm.ExecTx(app.cache, *tx, true, app.evc)
if err != nil {
return tmsp.NewError(tmsp.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err))
}
// TODO: need to return receipt so rpc.ResultBroadcastTx.Data (or Log) is the receipt
return tmsp.NewResultOK(nil, "Success")
// Implements manager/types.Application
func (app *ErisMint) CheckTx(txBytes []byte) (res tmsp.Result) {
var n int
var err error
tx := new(types.Tx)
buf := bytes.NewBuffer(txBytes)
wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err)
if err != nil {
return tmsp.NewError(tmsp.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err))
}
log.Info("CheckTx", "tx", *tx)
// TODO: make errors tmsp aware
err = sm.ExecTx(app.checkCache, *tx, false, nil)
if err != nil {
return tmsp.NewError(tmsp.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err))
}
// TODO: need to return receipt so rpc.ResultBroadcastTx.Data (or Log) is the receipt
return tmsp.NewResultOK(nil, "Success")
// Implements manager/types.Application
// Commit the state (called at end of block)
// NOTE: CheckTx/AppendTx must not run concurrently with Commit -
// the mempool should run during AppendTxs, but lock for Commit and Update
func (app *ErisMint) Commit() (res tmsp.Result) {
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
app.mtx.Lock() // the lock protects app.state
defer app.mtx.Unlock()
app.state.LastBlockHeight += 1
log.WithFields(log.Fields{
"blockheight" : app.state.LastBlockHeight,
}).Info("Commit block")
// sync the AppendTx cache
app.cache.Sync()
// if there were any txs in the block,
// reset the check cache to the new height
if app.nTxs > 0 {
log.WithFields(log.Fields{
"txs" : app.nTxs,
}).Info("Reset checkCache")
app.checkCache = sm.NewBlockCache(app.state)
}
app.nTxs = 0
// save state to disk
app.state.Save()
// flush events to listeners (XXX: note issue with blocking)
app.evc.Flush()
return tmsp.NewResultOK(app.state.Hash(), "Success")
func (app *ErisMint) Query(query []byte) (res tmsp.Result) {
return tmsp.NewResultOK(nil, "Success")