Release v0.9.1pull/340/merge v0.9.1
@ -0,0 +1,27 @@ | |||
package commands | |||
import ( | |||
"fmt" | |||
"github.com/spf13/cobra" | |||
"github.com/tendermint/go-wire" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
var genValidatorCmd = &cobra.Command{ | |||
Use: "gen_validator", | |||
Short: "Generate new validator keypair", | |||
Run: genValidator, | |||
} | |||
func init() { | |||
RootCmd.AddCommand(genValidatorCmd) | |||
} | |||
func genValidator(cmd *cobra.Command, args []string) { | |||
privValidator := types.GenPrivValidator() | |||
privValidatorJSONBytes := wire.JSONBytesPretty(privValidator) | |||
fmt.Printf(`%v | |||
`, string(privValidatorJSONBytes)) | |||
} |
@ -0,0 +1,40 @@ | |||
package commands | |||
import ( | |||
"fmt" | |||
"github.com/tendermint/tendermint/consensus" | |||
"github.com/spf13/cobra" | |||
) | |||
var replayCmd = &cobra.Command{ | |||
Use: "replay [walfile]", | |||
Short: "Replay messages from WAL", | |||
Run: func(cmd *cobra.Command, args []string) { | |||
if len(args) > 1 { | |||
consensus.RunReplayFile(config, args[1], false) | |||
} else { | |||
fmt.Println("replay requires an argument (walfile)") | |||
} | |||
}, | |||
} | |||
var replayConsoleCmd = &cobra.Command{ | |||
Use: "replay_console [walfile]", | |||
Short: "Replay messages from WAL in a console", | |||
Run: func(cmd *cobra.Command, args []string) { | |||
if len(args) > 1 { | |||
consensus.RunReplayFile(config, args[1], true) | |||
} else { | |||
fmt.Println("replay_console requires an argument (walfile)") | |||
} | |||
}, | |||
} | |||
func init() { | |||
RootCmd.AddCommand(replayCmd) | |||
RootCmd.AddCommand(replayConsoleCmd) | |||
} |
@ -0,0 +1,62 @@ | |||
package commands | |||
import ( | |||
"os" | |||
"github.com/spf13/cobra" | |||
cfg "github.com/tendermint/go-config" | |||
"github.com/tendermint/log15" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
var resetAllCmd = &cobra.Command{ | |||
Use: "unsafe_reset_all", | |||
Short: "(unsafe) Remove all the data and WAL, reset this node's validator", | |||
Run: resetAll, | |||
} | |||
var resetPrivValidatorCmd = &cobra.Command{ | |||
Use: "unsafe_reset_priv_validator", | |||
Short: "(unsafe) Reset this node's validator", | |||
Run: resetPrivValidator, | |||
} | |||
func init() { | |||
RootCmd.AddCommand(resetAllCmd) | |||
RootCmd.AddCommand(resetPrivValidatorCmd) | |||
} | |||
// XXX: this is totally unsafe. | |||
// it's only suitable for testnets. | |||
func resetAll(cmd *cobra.Command, args []string) { | |||
ResetAll(config, log) | |||
} | |||
// XXX: this is totally unsafe. | |||
// it's only suitable for testnets. | |||
func resetPrivValidator(cmd *cobra.Command, args []string) { | |||
ResetPrivValidator(config, log) | |||
} | |||
// Exported so other CLI tools can use it | |||
func ResetAll(c cfg.Config, l log15.Logger) { | |||
ResetPrivValidator(c, l) | |||
os.RemoveAll(c.GetString("db_dir")) | |||
} | |||
func ResetPrivValidator(c cfg.Config, l log15.Logger) { | |||
// Get PrivValidator | |||
var privValidator *types.PrivValidator | |||
privValidatorFile := config.GetString("priv_validator_file") | |||
if _, err := os.Stat(privValidatorFile); err == nil { | |||
privValidator = types.LoadPrivValidator(privValidatorFile) | |||
privValidator.Reset() | |||
log.Notice("Reset PrivValidator", "file", privValidatorFile) | |||
} else { | |||
privValidator = types.GenPrivValidator() | |||
privValidator.SetFile(privValidatorFile) | |||
privValidator.Save() | |||
log.Notice("Generated PrivValidator", "file", privValidatorFile) | |||
} | |||
} |
@ -0,0 +1,31 @@ | |||
package commands | |||
import ( | |||
"github.com/spf13/cobra" | |||
"github.com/tendermint/go-logger" | |||
tmcfg "github.com/tendermint/tendermint/config/tendermint" | |||
) | |||
var ( | |||
config = tmcfg.GetConfig("") | |||
log = logger.New("module", "main") | |||
) | |||
//global flag | |||
var logLevel string | |||
var RootCmd = &cobra.Command{ | |||
Use: "tendermint", | |||
Short: "Tendermint Core (BFT Consensus) in Go", | |||
PersistentPreRun: func(cmd *cobra.Command, args []string) { | |||
// set the log level in the config and logger | |||
config.Set("log_level", logLevel) | |||
logger.SetLogLevel(logLevel) | |||
}, | |||
} | |||
func init() { | |||
//parse flag and set config | |||
RootCmd.PersistentFlags().StringVar(&logLevel, "log_level", config.GetString("log_level"), "Log level") | |||
} |
@ -0,0 +1,124 @@ | |||
package commands | |||
import ( | |||
"io/ioutil" | |||
"time" | |||
"github.com/spf13/cobra" | |||
. "github.com/tendermint/go-common" | |||
"github.com/tendermint/tendermint/node" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
var runNodeCmd = &cobra.Command{ | |||
Use: "node", | |||
Short: "Run the tendermint node", | |||
PreRun: setConfigFlags, | |||
Run: runNode, | |||
} | |||
//flags | |||
var ( | |||
moniker string | |||
nodeLaddr string | |||
seeds string | |||
fastSync bool | |||
skipUPNP bool | |||
rpcLaddr string | |||
grpcLaddr string | |||
proxyApp string | |||
abciTransport string | |||
pex bool | |||
) | |||
func init() { | |||
// configuration options | |||
runNodeCmd.Flags().StringVar(&moniker, "moniker", config.GetString("moniker"), | |||
"Node Name") | |||
runNodeCmd.Flags().StringVar(&nodeLaddr, "node_laddr", config.GetString("node_laddr"), | |||
"Node listen address. (0.0.0.0:0 means any interface, any port)") | |||
runNodeCmd.Flags().StringVar(&seeds, "seeds", config.GetString("seeds"), | |||
"Comma delimited host:port seed nodes") | |||
runNodeCmd.Flags().BoolVar(&fastSync, "fast_sync", config.GetBool("fast_sync"), | |||
"Fast blockchain syncing") | |||
runNodeCmd.Flags().BoolVar(&skipUPNP, "skip_upnp", config.GetBool("skip_upnp"), | |||
"Skip UPNP configuration") | |||
runNodeCmd.Flags().StringVar(&rpcLaddr, "rpc_laddr", config.GetString("rpc_laddr"), | |||
"RPC listen address. Port required") | |||
runNodeCmd.Flags().StringVar(&grpcLaddr, "grpc_laddr", config.GetString("grpc_laddr"), | |||
"GRPC listen address (BroadcastTx only). Port required") | |||
runNodeCmd.Flags().StringVar(&proxyApp, "proxy_app", config.GetString("proxy_app"), | |||
"Proxy app address, or 'nilapp' or 'dummy' for local testing.") | |||
runNodeCmd.Flags().StringVar(&abciTransport, "abci", config.GetString("abci"), | |||
"Specify abci transport (socket | grpc)") | |||
// feature flags | |||
runNodeCmd.Flags().BoolVar(&pex, "pex", config.GetBool("pex_reactor"), | |||
"Enable Peer-Exchange (dev feature)") | |||
RootCmd.AddCommand(runNodeCmd) | |||
} | |||
func setConfigFlags(cmd *cobra.Command, args []string) { | |||
// Merge parsed flag values onto config | |||
config.Set("moniker", moniker) | |||
config.Set("node_laddr", nodeLaddr) | |||
config.Set("seeds", seeds) | |||
config.Set("fast_sync", fastSync) | |||
config.Set("skip_upnp", skipUPNP) | |||
config.Set("rpc_laddr", rpcLaddr) | |||
config.Set("grpc_laddr", grpcLaddr) | |||
config.Set("proxy_app", proxyApp) | |||
config.Set("abci", abciTransport) | |||
config.Set("pex_reactor", pex) | |||
} | |||
// Users wishing to: | |||
// * Use an external signer for their validators | |||
// * Supply an in-proc abci app | |||
// should import github.com/tendermint/tendermint/node and implement | |||
// their own run_node to call node.NewNode (instead of node.NewNodeDefault) | |||
// with their custom priv validator and/or custom proxy.ClientCreator | |||
func runNode(cmd *cobra.Command, args []string) { | |||
// Wait until the genesis doc becomes available | |||
// This is for Mintnet compatibility. | |||
// TODO: If Mintnet gets deprecated or genesis_file is | |||
// always available, remove. | |||
genDocFile := config.GetString("genesis_file") | |||
if !FileExists(genDocFile) { | |||
log.Notice(Fmt("Waiting for genesis file %v...", genDocFile)) | |||
for { | |||
time.Sleep(time.Second) | |||
if !FileExists(genDocFile) { | |||
continue | |||
} | |||
jsonBlob, err := ioutil.ReadFile(genDocFile) | |||
if err != nil { | |||
Exit(Fmt("Couldn't read GenesisDoc file: %v", err)) | |||
} | |||
genDoc, err := types.GenesisDocFromJSON(jsonBlob) | |||
if err != nil { | |||
Exit(Fmt("Error reading GenesisDoc: %v", err)) | |||
} | |||
if genDoc.ChainID == "" { | |||
Exit(Fmt("Genesis doc %v must include non-empty chain_id", genDocFile)) | |||
} | |||
config.Set("chain_id", genDoc.ChainID) | |||
} | |||
} | |||
// Create & start node | |||
n := node.NewNodeDefault(config) | |||
if _, err := n.Start(); err != nil { | |||
Exit(Fmt("Failed to start node: %v", err)) | |||
} else { | |||
log.Notice("Started node", "nodeInfo", n.Switch().NodeInfo()) | |||
} | |||
// Trap signal, run forever. | |||
n.RunForever() | |||
} |
@ -0,0 +1,93 @@ | |||
package commands | |||
import ( | |||
"fmt" | |||
"path" | |||
"time" | |||
"github.com/spf13/cobra" | |||
cmn "github.com/tendermint/go-common" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
var testnetFilesCmd = &cobra.Command{ | |||
Use: "testnet", | |||
Short: "Initialize files for a Tendermint testnet", | |||
Run: testnetFiles, | |||
} | |||
//flags | |||
var ( | |||
nValidators int | |||
dataDir string | |||
) | |||
func init() { | |||
testnetFilesCmd.Flags().IntVar(&nValidators, "n", 4, | |||
"Number of validators to initialize the testnet with") | |||
testnetFilesCmd.Flags().StringVar(&dataDir, "dir", "mytestnet", | |||
"Directory to store initialization data for the testnet") | |||
RootCmd.AddCommand(testnetFilesCmd) | |||
} | |||
func testnetFiles(cmd *cobra.Command, args []string) { | |||
genVals := make([]types.GenesisValidator, nValidators) | |||
// Initialize core dir and priv_validator.json's | |||
for i := 0; i < nValidators; i++ { | |||
mach := cmn.Fmt("mach%d", i) | |||
err := initMachCoreDirectory(dataDir, mach) | |||
if err != nil { | |||
cmn.Exit(err.Error()) | |||
} | |||
// Read priv_validator.json to populate vals | |||
privValFile := path.Join(dataDir, mach, "priv_validator.json") | |||
privVal := types.LoadPrivValidator(privValFile) | |||
genVals[i] = types.GenesisValidator{ | |||
PubKey: privVal.PubKey, | |||
Amount: 1, | |||
Name: mach, | |||
} | |||
} | |||
// Generate genesis doc from generated validators | |||
genDoc := &types.GenesisDoc{ | |||
GenesisTime: time.Now(), | |||
ChainID: "chain-" + cmn.RandStr(6), | |||
Validators: genVals, | |||
} | |||
// Write genesis file. | |||
for i := 0; i < nValidators; i++ { | |||
mach := cmn.Fmt("mach%d", i) | |||
genDoc.SaveAs(path.Join(dataDir, mach, "genesis.json")) | |||
} | |||
fmt.Println(cmn.Fmt("Successfully initialized %v node directories", nValidators)) | |||
} | |||
// Initialize per-machine core directory | |||
func initMachCoreDirectory(base, mach string) error { | |||
dir := path.Join(base, mach) | |||
err := cmn.EnsureDir(dir, 0777) | |||
if err != nil { | |||
return err | |||
} | |||
// Create priv_validator.json file if not present | |||
ensurePrivValidator(path.Join(dir, "priv_validator.json")) | |||
return nil | |||
} | |||
func ensurePrivValidator(file string) { | |||
if cmn.FileExists(file) { | |||
return | |||
} | |||
privValidator := types.GenPrivValidator() | |||
privValidator.SetFile(file) | |||
privValidator.Save() | |||
} |
@ -0,0 +1,21 @@ | |||
package commands | |||
import ( | |||
"fmt" | |||
"github.com/spf13/cobra" | |||
"github.com/tendermint/tendermint/version" | |||
) | |||
var versionCmd = &cobra.Command{ | |||
Use: "version", | |||
Short: "Show version info", | |||
Run: func(cmd *cobra.Command, args []string) { | |||
fmt.Println(version.Version) | |||
}, | |||
} | |||
func init() { | |||
RootCmd.AddCommand(versionCmd) | |||
} |
@ -1,66 +0,0 @@ | |||
package main | |||
import ( | |||
flag "github.com/spf13/pflag" | |||
"os" | |||
cfg "github.com/tendermint/go-config" | |||
) | |||
func parseFlags(config cfg.Config, args []string) { | |||
var ( | |||
printHelp bool | |||
moniker string | |||
nodeLaddr string | |||
seeds string | |||
fastSync bool | |||
skipUPNP bool | |||
rpcLaddr string | |||
grpcLaddr string | |||
logLevel string | |||
proxyApp string | |||
abciTransport string | |||
pex bool | |||
) | |||
// Declare flags | |||
var flags = flag.NewFlagSet("main", flag.ExitOnError) | |||
flags.BoolVar(&printHelp, "help", false, "Print this help message.") | |||
// configuration options | |||
flags.StringVar(&moniker, "moniker", config.GetString("moniker"), "Node Name") | |||
flags.StringVar(&nodeLaddr, "node_laddr", config.GetString("node_laddr"), "Node listen address. (0.0.0.0:0 means any interface, any port)") | |||
flags.StringVar(&seeds, "seeds", config.GetString("seeds"), "Comma delimited host:port seed nodes") | |||
flags.BoolVar(&fastSync, "fast_sync", config.GetBool("fast_sync"), "Fast blockchain syncing") | |||
flags.BoolVar(&skipUPNP, "skip_upnp", config.GetBool("skip_upnp"), "Skip UPNP configuration") | |||
flags.StringVar(&rpcLaddr, "rpc_laddr", config.GetString("rpc_laddr"), "RPC listen address. Port required") | |||
flags.StringVar(&grpcLaddr, "grpc_laddr", config.GetString("grpc_laddr"), "GRPC listen address (BroadcastTx only). Port required") | |||
flags.StringVar(&logLevel, "log_level", config.GetString("log_level"), "Log level") | |||
flags.StringVar(&proxyApp, "proxy_app", config.GetString("proxy_app"), | |||
"Proxy app address, or 'nilapp' or 'dummy' for local testing.") | |||
flags.StringVar(&abciTransport, "abci", config.GetString("abci"), "Specify abci transport (socket | grpc)") | |||
// feature flags | |||
flags.BoolVar(&pex, "pex", config.GetBool("pex_reactor"), "Enable Peer-Exchange (dev feature)") | |||
flags.Parse(args) | |||
if printHelp { | |||
flags.PrintDefaults() | |||
os.Exit(0) | |||
} | |||
// Merge parsed flag values onto app. | |||
config.Set("moniker", moniker) | |||
config.Set("node_laddr", nodeLaddr) | |||
config.Set("seeds", seeds) | |||
config.Set("fast_sync", fastSync) | |||
config.Set("skip_upnp", skipUPNP) | |||
config.Set("rpc_laddr", rpcLaddr) | |||
config.Set("grpc_laddr", grpcLaddr) | |||
config.Set("log_level", logLevel) | |||
config.Set("proxy_app", proxyApp) | |||
config.Set("abci", abciTransport) | |||
config.Set("pex_reactor", pex) | |||
} |
@ -1,15 +0,0 @@ | |||
package main | |||
import ( | |||
"fmt" | |||
"github.com/tendermint/go-wire" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
func gen_validator() { | |||
privValidator := types.GenPrivValidator() | |||
privValidatorJSONBytes := wire.JSONBytesPretty(privValidator) | |||
fmt.Printf(`%v | |||
`, string(privValidatorJSONBytes)) | |||
} |
@ -1,7 +0,0 @@ | |||
package main | |||
import ( | |||
"github.com/tendermint/go-logger" | |||
) | |||
var log = logger.New("module", "main") |
@ -1,33 +0,0 @@ | |||
package main | |||
import ( | |||
"os" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// XXX: this is totally unsafe. | |||
// it's only suitable for testnets. | |||
func reset_all() { | |||
reset_priv_validator() | |||
os.RemoveAll(config.GetString("db_dir")) | |||
os.Remove(config.GetString("cs_wal_file")) | |||
} | |||
// XXX: this is totally unsafe. | |||
// it's only suitable for testnets. | |||
func reset_priv_validator() { | |||
// Get PrivValidator | |||
var privValidator *types.PrivValidator | |||
privValidatorFile := config.GetString("priv_validator_file") | |||
if _, err := os.Stat(privValidatorFile); err == nil { | |||
privValidator = types.LoadPrivValidator(privValidatorFile) | |||
privValidator.Reset() | |||
log.Notice("Reset PrivValidator", "file", privValidatorFile) | |||
} else { | |||
privValidator = types.GenPrivValidator() | |||
privValidator.SetFile(privValidatorFile) | |||
privValidator.Save() | |||
log.Notice("Generated PrivValidator", "file", privValidatorFile) | |||
} | |||
} |
@ -1,59 +0,0 @@ | |||
package main | |||
import ( | |||
"io/ioutil" | |||
"time" | |||
. "github.com/tendermint/go-common" | |||
cfg "github.com/tendermint/go-config" | |||
"github.com/tendermint/tendermint/node" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// Users wishing to: | |||
// * Use an external signer for their validators | |||
// * Supply an in-proc abci app | |||
// should import github.com/tendermint/tendermint/node and implement | |||
// their own run_node to call node.NewNode (instead of node.NewNodeDefault) | |||
// with their custom priv validator and/or custom proxy.ClientCreator | |||
func run_node(config cfg.Config) { | |||
// Wait until the genesis doc becomes available | |||
// This is for Mintnet compatibility. | |||
// TODO: If Mintnet gets deprecated or genesis_file is | |||
// always available, remove. | |||
genDocFile := config.GetString("genesis_file") | |||
if !FileExists(genDocFile) { | |||
log.Notice(Fmt("Waiting for genesis file %v...", genDocFile)) | |||
for { | |||
time.Sleep(time.Second) | |||
if !FileExists(genDocFile) { | |||
continue | |||
} | |||
jsonBlob, err := ioutil.ReadFile(genDocFile) | |||
if err != nil { | |||
Exit(Fmt("Couldn't read GenesisDoc file: %v", err)) | |||
} | |||
genDoc, err := types.GenesisDocFromJSON(jsonBlob) | |||
if err != nil { | |||
Exit(Fmt("Error reading GenesisDoc: %v", err)) | |||
} | |||
if genDoc.ChainID == "" { | |||
Exit(Fmt("Genesis doc %v must include non-empty chain_id", genDocFile)) | |||
} | |||
config.Set("chain_id", genDoc.ChainID) | |||
} | |||
} | |||
// Create & start node | |||
n := node.NewNodeDefault(config) | |||
if _, err := n.Start(); err != nil { | |||
Exit(Fmt("Failed to start node: %v", err)) | |||
} else { | |||
log.Notice("Started node", "nodeInfo", n.Switch().NodeInfo()) | |||
} | |||
// Trap signal, run forever. | |||
n.RunForever() | |||
} |
@ -0,0 +1,43 @@ | |||
package core | |||
import ( | |||
"fmt" | |||
ctypes "github.com/tendermint/tendermint/rpc/core/types" | |||
"github.com/tendermint/tendermint/state/txindex/null" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { | |||
// if index is disabled, return error | |||
if _, ok := txIndexer.(*null.TxIndex); ok { | |||
return nil, fmt.Errorf("Transaction indexing is disabled.") | |||
} | |||
r, err := txIndexer.Get(hash) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if r == nil { | |||
return nil, fmt.Errorf("Tx (%X) not found", hash) | |||
} | |||
height := int(r.Height) // XXX | |||
index := int(r.Index) | |||
var proof types.TxProof | |||
if prove { | |||
block := blockStore.LoadBlock(height) | |||
proof = block.Data.Txs.Proof(index) | |||
} | |||
return &ctypes.ResultTx{ | |||
Height: height, | |||
Index: index, | |||
TxResult: r.Result, | |||
Tx: r.Tx, | |||
Proof: proof, | |||
}, nil | |||
} |
@ -0,0 +1,38 @@ | |||
package core_types | |||
import ( | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/tendermint/go-p2p" | |||
) | |||
func TestStatusIndexer(t *testing.T) { | |||
assert := assert.New(t) | |||
var status *ResultStatus | |||
assert.False(status.TxIndexEnabled()) | |||
status = &ResultStatus{} | |||
assert.False(status.TxIndexEnabled()) | |||
status.NodeInfo = &p2p.NodeInfo{} | |||
assert.False(status.TxIndexEnabled()) | |||
cases := []struct { | |||
expected bool | |||
other []string | |||
}{ | |||
{false, nil}, | |||
{false, []string{}}, | |||
{false, []string{"a=b"}}, | |||
{false, []string{"tx_indexiskv", "some=dood"}}, | |||
{true, []string{"tx_index=on", "tx_index=other"}}, | |||
{true, []string{"^(*^(", "tx_index=on", "a=n=b=d="}}, | |||
} | |||
for _, tc := range cases { | |||
status.NodeInfo.Other = tc.other | |||
assert.Equal(tc.expected, status.TxIndexEnabled()) | |||
} | |||
} |
@ -0,0 +1,20 @@ | |||
#!/usr/bin/env bash | |||
set -eu | |||
VERSION=$1 | |||
DIST_DIR=$2 # ./build/dist | |||
# Get the version from the environment, or try to figure it out. | |||
if [ -z $VERSION ]; then | |||
VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) | |||
fi | |||
if [ -z "$VERSION" ]; then | |||
echo "Please specify a version." | |||
exit 1 | |||
fi | |||
# copy to s3 | |||
aws s3 cp --recursive ${DIST_DIR} s3://tendermint/${VERSION} --acl public-read --exclude "*" --include "*.zip" | |||
aws s3 cp ${DIST_DIR}/tendermint_${VERSION}_SHA256SUMS s3://tendermint/0.9.0 --acl public-read | |||
exit 0 |
@ -0,0 +1,90 @@ | |||
package state | |||
import ( | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
"github.com/tendermint/abci/example/dummy" | |||
crypto "github.com/tendermint/go-crypto" | |||
dbm "github.com/tendermint/go-db" | |||
cfg "github.com/tendermint/tendermint/config/tendermint_test" | |||
"github.com/tendermint/tendermint/mempool" | |||
"github.com/tendermint/tendermint/proxy" | |||
"github.com/tendermint/tendermint/state/txindex" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
var ( | |||
privKey = crypto.GenPrivKeyEd25519FromSecret([]byte("execution_test")) | |||
chainID = "execution_chain" | |||
testPartSize = 65536 | |||
nTxsPerBlock = 10 | |||
) | |||
func TestApplyBlock(t *testing.T) { | |||
cc := proxy.NewLocalClientCreator(dummy.NewDummyApplication()) | |||
config := cfg.ResetConfig("execution_test_") | |||
proxyApp := proxy.NewAppConns(config, cc, nil) | |||
_, err := proxyApp.Start() | |||
require.Nil(t, err) | |||
defer proxyApp.Stop() | |||
mempool := mempool.NewMempool(config, proxyApp.Mempool()) | |||
state := state() | |||
indexer := &dummyIndexer{0} | |||
state.TxIndexer = indexer | |||
// make block | |||
block := makeBlock(1, state) | |||
err = state.ApplyBlock(nil, proxyApp.Consensus(), block, block.MakePartSet(testPartSize).Header(), mempool) | |||
require.Nil(t, err) | |||
assert.Equal(t, nTxsPerBlock, indexer.Indexed) // test indexing works | |||
// TODO check state and mempool | |||
} | |||
//---------------------------------------------------------------------------- | |||
// make some bogus txs | |||
func makeTxs(blockNum int) (txs []types.Tx) { | |||
for i := 0; i < nTxsPerBlock; i++ { | |||
txs = append(txs, types.Tx([]byte{byte(blockNum), byte(i)})) | |||
} | |||
return txs | |||
} | |||
func state() *State { | |||
return MakeGenesisState(dbm.NewMemDB(), &types.GenesisDoc{ | |||
ChainID: chainID, | |||
Validators: []types.GenesisValidator{ | |||
types.GenesisValidator{privKey.PubKey(), 10000, "test"}, | |||
}, | |||
AppHash: nil, | |||
}) | |||
} | |||
func makeBlock(num int, state *State) *types.Block { | |||
prevHash := state.LastBlockID.Hash | |||
prevParts := types.PartSetHeader{} | |||
valHash := state.Validators.Hash() | |||
prevBlockID := types.BlockID{prevHash, prevParts} | |||
block, _ := types.MakeBlock(num, chainID, makeTxs(num), new(types.Commit), | |||
prevBlockID, valHash, state.AppHash, testPartSize) | |||
return block | |||
} | |||
// dummyIndexer increments counter every time we index transaction. | |||
type dummyIndexer struct { | |||
Indexed int | |||
} | |||
func (indexer *dummyIndexer) Get(hash []byte) (*types.TxResult, error) { | |||
return nil, nil | |||
} | |||
func (indexer *dummyIndexer) AddBatch(batch *txindex.Batch) error { | |||
indexer.Indexed += batch.Size() | |||
return nil | |||
} |
@ -0,0 +1,57 @@ | |||
package txindex | |||
import ( | |||
"errors" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// Indexer interface defines methods to index and search transactions. | |||
type TxIndexer interface { | |||
// Batch analyzes, indexes or stores a batch of transactions. | |||
// | |||
// NOTE We do not specify Index method for analyzing a single transaction | |||
// here because it bears heavy perfomance loses. Almost all advanced indexers | |||
// support batching. | |||
AddBatch(b *Batch) error | |||
// Tx returns specified transaction or nil if the transaction is not indexed | |||
// or stored. | |||
Get(hash []byte) (*types.TxResult, error) | |||
} | |||
//---------------------------------------------------- | |||
// Txs are written as a batch | |||
// A Batch groups together multiple Index operations you would like performed | |||
// at the same time. The Batch structure is NOT thread-safe. You should only | |||
// perform operations on a batch from a single thread at a time. Once batch | |||
// execution has started, you may not modify it. | |||
type Batch struct { | |||
Ops []types.TxResult | |||
} | |||
// NewBatch creates a new Batch. | |||
func NewBatch(n int) *Batch { | |||
return &Batch{ | |||
Ops: make([]types.TxResult, n), | |||
} | |||
} | |||
// Index adds or updates entry for the given result.Index. | |||
func (b *Batch) Add(result types.TxResult) error { | |||
b.Ops[result.Index] = result | |||
return nil | |||
} | |||
// Size returns the total number of operations inside the batch. | |||
func (b *Batch) Size() int { | |||
return len(b.Ops) | |||
} | |||
//---------------------------------------------------- | |||
// Errors | |||
// ErrorEmptyHash indicates empty hash | |||
var ErrorEmptyHash = errors.New("Transaction hash cannot be empty") |
@ -0,0 +1,56 @@ | |||
package kv | |||
import ( | |||
"bytes" | |||
"fmt" | |||
db "github.com/tendermint/go-db" | |||
"github.com/tendermint/go-wire" | |||
"github.com/tendermint/tendermint/state/txindex" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// TxIndex is the simplest possible indexer, backed by Key-Value storage (levelDB). | |||
// It could only index transaction by its identifier. | |||
type TxIndex struct { | |||
store db.DB | |||
} | |||
// NewTxIndex returns new instance of TxIndex. | |||
func NewTxIndex(store db.DB) *TxIndex { | |||
return &TxIndex{store: store} | |||
} | |||
// Get gets transaction from the TxIndex storage and returns it or nil if the | |||
// transaction is not found. | |||
func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) { | |||
if len(hash) == 0 { | |||
return nil, txindex.ErrorEmptyHash | |||
} | |||
rawBytes := txi.store.Get(hash) | |||
if rawBytes == nil { | |||
return nil, nil | |||
} | |||
r := bytes.NewReader(rawBytes) | |||
var n int | |||
var err error | |||
txResult := wire.ReadBinary(&types.TxResult{}, r, 0, &n, &err).(*types.TxResult) | |||
if err != nil { | |||
return nil, fmt.Errorf("Error reading TxResult: %v", err) | |||
} | |||
return txResult, nil | |||
} | |||
// Batch writes a batch of transactions into the TxIndex storage. | |||
func (txi *TxIndex) AddBatch(b *txindex.Batch) error { | |||
storeBatch := txi.store.NewBatch() | |||
for _, result := range b.Ops { | |||
rawBytes := wire.BinaryBytes(&result) | |||
storeBatch.Set(result.Tx.Hash(), rawBytes) | |||
} | |||
storeBatch.Write() | |||
return nil | |||
} |
@ -0,0 +1,63 @@ | |||
package kv | |||
import ( | |||
"io/ioutil" | |||
"os" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
"github.com/stretchr/testify/require" | |||
abci "github.com/tendermint/abci/types" | |||
db "github.com/tendermint/go-db" | |||
"github.com/tendermint/tendermint/state/txindex" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
func TestTxIndex(t *testing.T) { | |||
indexer := &TxIndex{store: db.NewMemDB()} | |||
tx := types.Tx("HELLO WORLD") | |||
txResult := &types.TxResult{1, 0, tx, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeType_OK, Log: ""}} | |||
hash := tx.Hash() | |||
batch := txindex.NewBatch(1) | |||
batch.Add(*txResult) | |||
err := indexer.AddBatch(batch) | |||
require.Nil(t, err) | |||
loadedTxResult, err := indexer.Get(hash) | |||
require.Nil(t, err) | |||
assert.Equal(t, txResult, loadedTxResult) | |||
} | |||
func benchmarkTxIndex(txsCount int, b *testing.B) { | |||
tx := types.Tx("HELLO WORLD") | |||
txResult := &types.TxResult{1, 0, tx, abci.ResponseDeliverTx{Data: []byte{0}, Code: abci.CodeType_OK, Log: ""}} | |||
dir, err := ioutil.TempDir("", "tx_index_db") | |||
if err != nil { | |||
b.Fatal(err) | |||
} | |||
defer os.RemoveAll(dir) | |||
store := db.NewDB("tx_index", "leveldb", dir) | |||
indexer := &TxIndex{store: store} | |||
batch := txindex.NewBatch(txsCount) | |||
for i := 0; i < txsCount; i++ { | |||
txResult.Index += 1 | |||
batch.Add(*txResult) | |||
} | |||
b.ResetTimer() | |||
for n := 0; n < b.N; n++ { | |||
err = indexer.AddBatch(batch) | |||
} | |||
} | |||
func BenchmarkTxIndex1(b *testing.B) { benchmarkTxIndex(1, b) } | |||
func BenchmarkTxIndex500(b *testing.B) { benchmarkTxIndex(500, b) } | |||
func BenchmarkTxIndex1000(b *testing.B) { benchmarkTxIndex(1000, b) } | |||
func BenchmarkTxIndex2000(b *testing.B) { benchmarkTxIndex(2000, b) } | |||
func BenchmarkTxIndex10000(b *testing.B) { benchmarkTxIndex(10000, b) } |
@ -0,0 +1,21 @@ | |||
package null | |||
import ( | |||
"errors" | |||
"github.com/tendermint/tendermint/state/txindex" | |||
"github.com/tendermint/tendermint/types" | |||
) | |||
// TxIndex acts as a /dev/null. | |||
type TxIndex struct{} | |||
// Tx panics. | |||
func (txi *TxIndex) Get(hash []byte) (*types.TxResult, error) { | |||
return nil, errors.New(`Indexing is disabled (set 'tx_index = "kv"' in config)`) | |||
} | |||
// Batch returns nil. | |||
func (txi *TxIndex) AddBatch(batch *txindex.Batch) error { | |||
return nil | |||
} |
@ -0,0 +1,122 @@ | |||
package types | |||
import ( | |||
"bytes" | |||
"testing" | |||
"github.com/stretchr/testify/assert" | |||
cmn "github.com/tendermint/go-common" | |||
ctest "github.com/tendermint/go-common/test" | |||
wire "github.com/tendermint/go-wire" | |||
) | |||
func makeTxs(cnt, size int) Txs { | |||
txs := make(Txs, cnt) | |||
for i := 0; i < cnt; i++ { | |||
txs[i] = cmn.RandBytes(size) | |||
} | |||
return txs | |||
} | |||
func randInt(low, high int) int { | |||
off := cmn.RandInt() % (high - low) | |||
return low + off | |||
} | |||
func TestTxIndex(t *testing.T) { | |||
assert := assert.New(t) | |||
for i := 0; i < 20; i++ { | |||
txs := makeTxs(15, 60) | |||
for j := 0; j < len(txs); j++ { | |||
tx := txs[j] | |||
idx := txs.Index(tx) | |||
assert.Equal(j, idx) | |||
} | |||
assert.Equal(-1, txs.Index(nil)) | |||
assert.Equal(-1, txs.Index(Tx("foodnwkf"))) | |||
} | |||
} | |||
func TestValidTxProof(t *testing.T) { | |||
assert := assert.New(t) | |||
cases := []struct { | |||
txs Txs | |||
}{ | |||
{Txs{{1, 4, 34, 87, 163, 1}}}, | |||
{Txs{{5, 56, 165, 2}, {4, 77}}}, | |||
{Txs{Tx("foo"), Tx("bar"), Tx("baz")}}, | |||
{makeTxs(20, 5)}, | |||
{makeTxs(7, 81)}, | |||
{makeTxs(61, 15)}, | |||
} | |||
for h, tc := range cases { | |||
txs := tc.txs | |||
root := txs.Hash() | |||
// make sure valid proof for every tx | |||
for i := range txs { | |||
leaf := txs[i] | |||
leafHash := leaf.Hash() | |||
proof := txs.Proof(i) | |||
assert.Equal(i, proof.Index, "%d: %d", h, i) | |||
assert.Equal(len(txs), proof.Total, "%d: %d", h, i) | |||
assert.Equal(root, proof.RootHash, "%d: %d", h, i) | |||
assert.Equal(leaf, proof.Data, "%d: %d", h, i) | |||
assert.Equal(leafHash, proof.LeafHash(), "%d: %d", h, i) | |||
assert.Nil(proof.Validate(root), "%d: %d", h, i) | |||
assert.NotNil(proof.Validate([]byte("foobar")), "%d: %d", h, i) | |||
// read-write must also work | |||
var p2 TxProof | |||
bin := wire.BinaryBytes(proof) | |||
err := wire.ReadBinaryBytes(bin, &p2) | |||
if assert.Nil(err, "%d: %d: %+v", h, i, err) { | |||
assert.Nil(p2.Validate(root), "%d: %d", h, i) | |||
} | |||
} | |||
} | |||
} | |||
func TestTxProofUnchangable(t *testing.T) { | |||
// run the other test a bunch... | |||
for i := 0; i < 40; i++ { | |||
testTxProofUnchangable(t) | |||
} | |||
} | |||
func testTxProofUnchangable(t *testing.T) { | |||
assert := assert.New(t) | |||
// make some proof | |||
txs := makeTxs(randInt(2, 100), randInt(16, 128)) | |||
root := txs.Hash() | |||
i := randInt(0, len(txs)-1) | |||
proof := txs.Proof(i) | |||
// make sure it is valid to start with | |||
assert.Nil(proof.Validate(root)) | |||
bin := wire.BinaryBytes(proof) | |||
// try mutating the data and make sure nothing breaks | |||
for j := 0; j < 500; j++ { | |||
bad := ctest.MutateByteSlice(bin) | |||
if !bytes.Equal(bad, bin) { | |||
assertBadProof(t, root, bad, proof) | |||
} | |||
} | |||
} | |||
// this make sure the proof doesn't deserialize into something valid | |||
func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) { | |||
var proof TxProof | |||
err := wire.ReadBinaryBytes(bad, &proof) | |||
if err == nil { | |||
err = proof.Validate(root) | |||
if err == nil { | |||
// okay, this can happen if we have a slightly different total | |||
// (where the path ends up the same), if it is something else, we have | |||
// a real problem | |||
assert.NotEqual(t, proof.Total, good.Total, "bad: %#v\ngood: %#v", proof, good) | |||
} | |||
} | |||
} |