initial commit
This commit is contained in:
commit
0bd4061cf6
|
@ -0,0 +1,63 @@
|
|||
# Tendermint Streaming Protocol (TMSP)
|
||||
|
||||
**TMSP** is a socket protocol, which means applications can be written in any programming language.
|
||||
TMSP is an asynchronous streaming protocol: message responses are written back asynchronously to the platform.
|
||||
|
||||
*Applications must be deterministic.*
|
||||
|
||||
## Message types
|
||||
|
||||
#### AppendTx
|
||||
* __Arguments__:
|
||||
* `TxBytes ([]byte)`
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* __Usage__:<br/>
|
||||
Append and run a transaction. The transaction may or may not be final.
|
||||
|
||||
#### GetHash
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* `Hash ([]byte)`
|
||||
* __Usage__:<br/>
|
||||
Return a Merkle root hash of the application state
|
||||
|
||||
#### Commit
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* __Usage__:<br/>
|
||||
Finalize all appended transactions
|
||||
|
||||
#### Rollback
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* __Usage__:<br/>
|
||||
Roll back to the last commit
|
||||
|
||||
#### SetEventsMode
|
||||
* __Arguments__:
|
||||
* `EventsMode (int8)`:
|
||||
* `EventsModeOff (0)`: Events are not reported. Used for mempool.
|
||||
* `EventsModeCached (1)`: Events are cached.
|
||||
* `EventsModeOn (2)`: Flush cache and report events.
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* __Usage__:<br/>
|
||||
Set event reporting mode for future transactions
|
||||
|
||||
#### AddListener
|
||||
* __Arguments__:
|
||||
* `EventKey (string)`
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* __Usage__:<br/>
|
||||
Add event listener callback for events with given key.
|
||||
|
||||
#### RemoveListener
|
||||
* __Arguments__:
|
||||
* `EventKey (string)`
|
||||
* __Returns__:
|
||||
* `RetCode (int8)`
|
||||
* __Usage__:<br/>
|
||||
Remove event listener callback for events with given key.
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmsp/types"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "cli"
|
||||
app.Usage = "cli [command] [args...]"
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "address",
|
||||
Value: "tcp://127.0.0.1:8080",
|
||||
Usage: "address of application socket",
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "append_tx",
|
||||
Usage: "Append a new tx to application",
|
||||
Action: func(c *cli.Context) {
|
||||
cmdAppendTx(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "get_hash",
|
||||
Usage: "Get application Merkle root hash",
|
||||
Action: func(c *cli.Context) {
|
||||
cmdGetHash(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commit",
|
||||
Usage: "Commit the application state",
|
||||
Action: func(c *cli.Context) {
|
||||
cmdCommit(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "rollback",
|
||||
Usage: "Roll back the application state to the latest commit",
|
||||
Action: func(c *cli.Context) {
|
||||
cmdRollback(c)
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Run(os.Args)
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// Append a new tx to application
|
||||
func cmdAppendTx(c *cli.Context) {
|
||||
args := c.Args() // Args to AppendTx
|
||||
conn, err := Connect(c.GlobalString("address"))
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
res, err := write(conn, types.RequestAppendTx{[]byte(args[0])})
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
fmt.Println("Sent tx:", args[0], "response:", res)
|
||||
}
|
||||
|
||||
// Get application Merkle root hash
|
||||
func cmdGetHash(c *cli.Context) {
|
||||
conn, err := Connect(c.GlobalString("address"))
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
res, err := write(conn, types.RequestGetHash{})
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
fmt.Println("Got hash:", Fmt("%X", res.(types.ResponseGetHash).Hash))
|
||||
}
|
||||
|
||||
// Commit the application state
|
||||
func cmdCommit(c *cli.Context) {
|
||||
conn, err := Connect(c.GlobalString("address"))
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
_, err = write(conn, types.RequestCommit{})
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
fmt.Println("Committed.")
|
||||
}
|
||||
|
||||
// Roll back the application state to the latest commit
|
||||
func cmdRollback(c *cli.Context) {
|
||||
conn, err := Connect(c.GlobalString("address"))
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
_, err = write(conn, types.RequestRollback{})
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
fmt.Println("Rolled back.")
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func write(conn net.Conn, req types.Request) (types.Response, error) {
|
||||
var n int64
|
||||
var err error
|
||||
wire.WriteBinary(req, conn, &n, &err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res types.Response
|
||||
wire.ReadBinaryPtr(&res, conn, &n, &err)
|
||||
return res, err
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-merkle"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmsp/server"
|
||||
"github.com/tendermint/tmsp/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Start the listener
|
||||
_, err := server.StartListener("tcp://127.0.0.1:8080", &DummyApplication{})
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
|
||||
// Wait forever
|
||||
TrapSignal(func() {
|
||||
// Cleanup
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
type DummyApplication struct {
|
||||
state merkle.Tree
|
||||
lastCommitState merkle.Tree
|
||||
}
|
||||
|
||||
func NewDummyApplication() *DummyApplication {
|
||||
state := merkle.NewIAVLTree(
|
||||
wire.BasicCodec,
|
||||
wire.BasicCodec,
|
||||
0,
|
||||
nil,
|
||||
)
|
||||
return &DummyApplication{
|
||||
state: state,
|
||||
lastCommitState: state,
|
||||
}
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) Echo(message string) (types.RetCode, string) {
|
||||
return 0, message
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) AppendTx(tx []byte) types.RetCode {
|
||||
dapp.state.Set(tx, tx)
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) GetHash() ([]byte, types.RetCode) {
|
||||
hash := dapp.state.Hash()
|
||||
return hash, 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) Commit() types.RetCode {
|
||||
dapp.lastCommitState = dapp.state.Copy()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) Rollback() types.RetCode {
|
||||
dapp.state = dapp.lastCommitState.Copy()
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) SetEventsMode(mode types.EventsMode) types.RetCode {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) AddListener(key string) types.RetCode {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) RemListener(key string) types.RetCode {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (dapp *DummyApplication) GetEvents() []types.Event {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmsp/server"
|
||||
"github.com/tendermint/tmsp/types"
|
||||
)
|
||||
|
||||
func TestStream(t *testing.T) {
|
||||
|
||||
// Start the listener
|
||||
_, err := server.StartListener("tcp://127.0.0.1:8080", NewDummyApplication())
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
|
||||
// Connect to the socket
|
||||
conn, err := Connect("tcp://127.0.0.1:8080")
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
|
||||
// Read response data
|
||||
go func() {
|
||||
for {
|
||||
var n int64
|
||||
var err error
|
||||
var res types.Response
|
||||
wire.ReadBinaryPtr(&res, conn, &n, &err)
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
// fmt.Println("Read", n)
|
||||
}
|
||||
}()
|
||||
|
||||
// Write requests
|
||||
for {
|
||||
var n int64
|
||||
var err error
|
||||
var req types.Request = types.RequestAppendTx{TxBytes: []byte("test")}
|
||||
wire.WriteBinary(req, conn, &n, &err)
|
||||
if err != nil {
|
||||
Exit(err.Error())
|
||||
}
|
||||
// fmt.Println("Wrote", n)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
. "github.com/tendermint/go-common"
|
||||
"github.com/tendermint/go-wire"
|
||||
"github.com/tendermint/tmsp/types"
|
||||
)
|
||||
|
||||
func StartListener(protoAddr string, app types.Application) (net.Listener, error) {
|
||||
parts := strings.SplitN(protoAddr, "://", 2)
|
||||
proto, addr := parts[0], parts[1]
|
||||
ln, err := net.Listen(proto, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// A goroutine to accept a connection.
|
||||
go func() {
|
||||
|
||||
for {
|
||||
// Accept a connection
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
Exit("Failed to accept connection")
|
||||
} else {
|
||||
fmt.Println("Accepted a new connection")
|
||||
}
|
||||
connClosed := make(chan struct{}, 2) // Push to signal connection closed
|
||||
responses := make(chan types.Response, 1000) // A channel to buffer responses
|
||||
|
||||
// Read requests from conn and deal with them
|
||||
go handleRequests(app, connClosed, conn, responses)
|
||||
// Pull responses from 'responses' and write them to conn.
|
||||
go handleResponses(connClosed, responses, conn)
|
||||
|
||||
// Wait until connection is closed
|
||||
<-connClosed
|
||||
fmt.Println("Connection was closed. Waiting for new connection...")
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
// Read requests from conn and deal with them
|
||||
func handleRequests(app types.Application, connClosed chan struct{}, conn net.Conn, responses chan<- types.Response) {
|
||||
var count int
|
||||
for {
|
||||
var n int64
|
||||
var err error
|
||||
var req types.Request
|
||||
wire.ReadBinaryPtr(&req, conn, &n, &err)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
connClosed <- struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
if count%1000 == 0 {
|
||||
fmt.Println("Received request", reflect.TypeOf(req), req, n, err, count)
|
||||
}
|
||||
|
||||
handleRequest(app, req, responses)
|
||||
}
|
||||
}
|
||||
|
||||
func handleRequest(app types.Application, req types.Request, responses chan<- types.Response) {
|
||||
switch req := req.(type) {
|
||||
case types.RequestEcho:
|
||||
retCode, msg := app.Echo(req.Message)
|
||||
responses <- types.ResponseEcho{retCode, msg}
|
||||
case types.RequestAppendTx:
|
||||
retCode := app.AppendTx(req.TxBytes)
|
||||
responses <- types.ResponseAppendTx{retCode}
|
||||
events := app.GetEvents()
|
||||
for _, event := range events {
|
||||
responses <- types.ResponseEvent{event}
|
||||
}
|
||||
case types.RequestGetHash:
|
||||
hash, retCode := app.GetHash()
|
||||
responses <- types.ResponseGetHash{retCode, hash}
|
||||
case types.RequestCommit:
|
||||
retCode := app.Commit()
|
||||
responses <- types.ResponseCommit{retCode}
|
||||
case types.RequestRollback:
|
||||
retCode := app.Rollback()
|
||||
responses <- types.ResponseRollback{retCode}
|
||||
case types.RequestSetEventsMode:
|
||||
retCode := app.SetEventsMode(req.EventsMode)
|
||||
responses <- types.ResponseSetEventsMode{retCode}
|
||||
if req.EventsMode == types.EventsModeOn {
|
||||
events := app.GetEvents()
|
||||
for _, event := range events {
|
||||
responses <- types.ResponseEvent{event}
|
||||
}
|
||||
}
|
||||
case types.RequestAddListener:
|
||||
retCode := app.AddListener(req.EventKey)
|
||||
responses <- types.ResponseAddListener{retCode}
|
||||
case types.RequestRemListener:
|
||||
retCode := app.RemListener(req.EventKey)
|
||||
responses <- types.ResponseRemListener{retCode}
|
||||
default:
|
||||
responses <- types.ResponseException{"Unknown request"}
|
||||
}
|
||||
}
|
||||
|
||||
// Pull responses from 'responses' and write them to conn.
|
||||
func handleResponses(connClosed chan struct{}, responses <-chan types.Response, conn net.Conn) {
|
||||
var count int
|
||||
for {
|
||||
var res = <-responses
|
||||
var n int64
|
||||
var err error
|
||||
wire.WriteBinary(res, conn, &n, &err)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
connClosed <- struct{}{}
|
||||
return
|
||||
}
|
||||
|
||||
count++
|
||||
if count%1000 == 0 {
|
||||
fmt.Println("Sent response", reflect.TypeOf(res), res, n, err, count)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package types
|
||||
|
||||
type Application interface {
|
||||
|
||||
// Echo a message
|
||||
Echo(message string) (RetCode, string)
|
||||
|
||||
// Append a tx, which may or may not get committed
|
||||
AppendTx(tx []byte) RetCode
|
||||
|
||||
// Return the application Merkle root hash
|
||||
GetHash() ([]byte, RetCode)
|
||||
|
||||
// Set commit checkpoint
|
||||
Commit() RetCode
|
||||
|
||||
// Rollback to the latest commit
|
||||
Rollback() RetCode
|
||||
|
||||
// Set events reporting mode
|
||||
SetEventsMode(mode EventsMode) RetCode
|
||||
|
||||
// Add event listener
|
||||
AddListener(key string) RetCode
|
||||
|
||||
// Remove event listener
|
||||
RemListener(key string) RetCode
|
||||
|
||||
// Get all events
|
||||
GetEvents() []Event
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package types
|
||||
|
||||
type EventsMode int8
|
||||
|
||||
const (
|
||||
EventsModeOff = EventsMode(0)
|
||||
EventsModeCached = EventsMode(1)
|
||||
EventsModeOn = EventsMode(2)
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Key string
|
||||
TxBytes []byte
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package types
|
||||
|
||||
import "github.com/tendermint/go-wire"
|
||||
|
||||
const (
|
||||
requestTypeEcho = byte(0x01)
|
||||
requestTypeAppendTx = byte(0x02)
|
||||
requestTypeGetHash = byte(0x03)
|
||||
requestTypeCommit = byte(0x04)
|
||||
requestTypeRollback = byte(0x05)
|
||||
requestTypeSetEventsMode = byte(0x06)
|
||||
requestTypeAddListener = byte(0x07)
|
||||
requestTypeRemListener = byte(0x08)
|
||||
|
||||
responseTypeEcho = byte(0x11)
|
||||
responseTypeAppendTx = byte(0x12)
|
||||
responseTypeGetHash = byte(0x13)
|
||||
responseTypeCommit = byte(0x14)
|
||||
responseTypeRollback = byte(0x15)
|
||||
responseTypeSetEventsMode = byte(0x16)
|
||||
responseTypeAddListener = byte(0x17)
|
||||
responseTypeRemListener = byte(0x18)
|
||||
|
||||
responseTypeException = byte(0x20)
|
||||
responseTypeEvent = byte(0x21)
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
type RequestEcho struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
type RequestAppendTx struct {
|
||||
TxBytes []byte
|
||||
}
|
||||
|
||||
type RequestGetHash struct {
|
||||
}
|
||||
|
||||
type RequestCommit struct {
|
||||
}
|
||||
|
||||
type RequestRollback struct {
|
||||
}
|
||||
|
||||
type RequestSetEventsMode struct {
|
||||
EventsMode
|
||||
}
|
||||
|
||||
type RequestAddListener struct {
|
||||
EventKey string
|
||||
}
|
||||
|
||||
type RequestRemListener struct {
|
||||
EventKey string
|
||||
}
|
||||
|
||||
type Request interface {
|
||||
AssertRequestType()
|
||||
}
|
||||
|
||||
func (_ RequestEcho) AssertRequestType() {}
|
||||
func (_ RequestAppendTx) AssertRequestType() {}
|
||||
func (_ RequestGetHash) AssertRequestType() {}
|
||||
func (_ RequestCommit) AssertRequestType() {}
|
||||
func (_ RequestRollback) AssertRequestType() {}
|
||||
func (_ RequestSetEventsMode) AssertRequestType() {}
|
||||
func (_ RequestAddListener) AssertRequestType() {}
|
||||
func (_ RequestRemListener) AssertRequestType() {}
|
||||
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ Request }{},
|
||||
wire.ConcreteType{RequestEcho{}, requestTypeEcho},
|
||||
wire.ConcreteType{RequestAppendTx{}, requestTypeAppendTx},
|
||||
wire.ConcreteType{RequestGetHash{}, requestTypeGetHash},
|
||||
wire.ConcreteType{RequestCommit{}, requestTypeCommit},
|
||||
wire.ConcreteType{RequestRollback{}, requestTypeRollback},
|
||||
wire.ConcreteType{RequestSetEventsMode{}, requestTypeSetEventsMode},
|
||||
wire.ConcreteType{RequestAddListener{}, requestTypeAddListener},
|
||||
wire.ConcreteType{RequestRemListener{}, requestTypeRemListener},
|
||||
)
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
type ResponseEcho struct {
|
||||
RetCode
|
||||
Message string
|
||||
}
|
||||
|
||||
type ResponseAppendTx struct {
|
||||
RetCode
|
||||
}
|
||||
|
||||
type ResponseGetHash struct {
|
||||
RetCode
|
||||
Hash []byte
|
||||
}
|
||||
|
||||
type ResponseCommit struct {
|
||||
RetCode
|
||||
}
|
||||
|
||||
type ResponseRollback struct {
|
||||
RetCode
|
||||
}
|
||||
|
||||
type ResponseSetEventsMode struct {
|
||||
RetCode
|
||||
}
|
||||
|
||||
type ResponseAddListener struct {
|
||||
RetCode
|
||||
}
|
||||
|
||||
type ResponseRemListener struct {
|
||||
RetCode
|
||||
}
|
||||
|
||||
type ResponseException struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
type ResponseEvent struct {
|
||||
Event
|
||||
}
|
||||
|
||||
type Response interface {
|
||||
AssertResponseType()
|
||||
}
|
||||
|
||||
func (_ ResponseEcho) AssertResponseType() {}
|
||||
func (_ ResponseAppendTx) AssertResponseType() {}
|
||||
func (_ ResponseGetHash) AssertResponseType() {}
|
||||
func (_ ResponseCommit) AssertResponseType() {}
|
||||
func (_ ResponseRollback) AssertResponseType() {}
|
||||
func (_ ResponseSetEventsMode) AssertResponseType() {}
|
||||
func (_ ResponseAddListener) AssertResponseType() {}
|
||||
func (_ ResponseRemListener) AssertResponseType() {}
|
||||
func (_ ResponseException) AssertResponseType() {}
|
||||
func (_ ResponseEvent) AssertResponseType() {}
|
||||
|
||||
var _ = wire.RegisterInterface(
|
||||
struct{ Response }{},
|
||||
wire.ConcreteType{ResponseEcho{}, responseTypeEcho},
|
||||
wire.ConcreteType{ResponseAppendTx{}, responseTypeAppendTx},
|
||||
wire.ConcreteType{ResponseGetHash{}, responseTypeGetHash},
|
||||
wire.ConcreteType{ResponseCommit{}, responseTypeCommit},
|
||||
wire.ConcreteType{ResponseRollback{}, responseTypeRollback},
|
||||
wire.ConcreteType{ResponseSetEventsMode{}, responseTypeSetEventsMode},
|
||||
wire.ConcreteType{ResponseAddListener{}, responseTypeAddListener},
|
||||
wire.ConcreteType{ResponseRemListener{}, responseTypeRemListener},
|
||||
wire.ConcreteType{ResponseException{}, responseTypeException},
|
||||
wire.ConcreteType{ResponseEvent{}, responseTypeEvent},
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
package types
|
||||
|
||||
type RetCode int
|
||||
|
||||
// Reserved return codes
|
||||
const (
|
||||
RetCodeOK = RetCode(0)
|
||||
RetCodeInternalError = RetCode(1)
|
||||
RetCodeUnauthorized = RetCode(2)
|
||||
RetCodeInsufficientFees = RetCode(3)
|
||||
RetCodeUnknownRequest = RetCode(4)
|
||||
)
|
Loading…
Reference in New Issue