Skip to content
Snippets Groups Projects
eris-mint.go 6.18 KiB
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/>.

package erismint
  "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"
  log "github.com/eris-ltd/eris-logger"
  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.

type ErisMint struct {
  mtx sync.Mutex
  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
  nTxs int // count txs in a block
// 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 {
  return app.checkCache
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) {
  return "ErisDB"
// Implements manager/types.Application
func (app *ErisMint) SetOption(key string, value string) (log string) {
  return ""
// 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) {
  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")