Implement basic proof-of-work module & add to basecoin example
Module users specify a coin denomination and proof-of-work reward. Blockchain clients can then submit SHA256 Hashcash solutions and receive the reward, with a constantly increasing difficulty. Includes replay protection to prevent the same solution being submitted multiple times, and inclusion of the rewardee in the hash data to prevent others from submitting the same solution once they see it in the tx pool. Reasonably comprehensive testsuite
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/pow"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/sketchy"
|
||||
)
|
||||
|
||||
@@ -59,11 +60,13 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
|
||||
// add handlers
|
||||
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
|
||||
coolMapper := cool.NewMapper(app.capKeyMainStore)
|
||||
powMapper := pow.NewMapper(app.capKeyMainStore)
|
||||
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
|
||||
stakingMapper := staking.NewMapper(app.capKeyStakingStore)
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(coinKeeper)).
|
||||
AddRoute("cool", cool.NewHandler(coinKeeper, coolMapper)).
|
||||
AddRoute("pow", pow.NewHandler(coinKeeper, powMapper, pow.NewPowConfig("pow", int64(1)))).
|
||||
AddRoute("sketchy", sketchy.NewHandler()).
|
||||
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
|
||||
AddRoute("staking", staking.NewHandler(stakingMapper, coinKeeper))
|
||||
@@ -92,6 +95,7 @@ func MakeCodec() *wire.Codec {
|
||||
const msgTypeIBCReceiveMsg = 0x6
|
||||
const msgTypeBondMsg = 0x7
|
||||
const msgTypeUnbondMsg = 0x8
|
||||
const msgTypeMineMsg = 0x9
|
||||
var _ = oldwire.RegisterInterface(
|
||||
struct{ sdk.Msg }{},
|
||||
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
|
||||
@@ -102,6 +106,7 @@ func MakeCodec() *wire.Codec {
|
||||
oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
|
||||
oldwire.ConcreteType{staking.BondMsg{}, msgTypeBondMsg},
|
||||
oldwire.ConcreteType{staking.UnbondMsg{}, msgTypeUnbondMsg},
|
||||
oldwire.ConcreteType{pow.MineMsg{}, msgTypeMineMsg},
|
||||
)
|
||||
|
||||
const accTypeApp = 0x1
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/builder"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/pow"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
func MineCmd(cdc *wire.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "mine [difficulty] [count] [nonce] [solution]",
|
||||
Short: "Mine some coins with proof-of-work!",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 4 {
|
||||
return errors.New("You must provide a difficulty, a solution, and a nonce (in that order)")
|
||||
}
|
||||
|
||||
// get from address and parse arguments
|
||||
|
||||
from, err := builder.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
difficulty, err := strconv.ParseUint(args[0], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count, err := strconv.ParseUint(args[1], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonce, err := strconv.ParseUint(args[2], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
solution := []byte(args[3])
|
||||
|
||||
msg := pow.NewMineMsg(from, difficulty, count, nonce, solution)
|
||||
|
||||
// get account name
|
||||
name := viper.GetString(client.FlagName)
|
||||
|
||||
// get password
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type CodeType = sdk.CodeType
|
||||
|
||||
const (
|
||||
CodeInvalidDifficulty CodeType = 201
|
||||
CodeNonexistentDifficulty CodeType = 202
|
||||
CodeNonexistentReward CodeType = 203
|
||||
CodeNonexistentCount CodeType = 204
|
||||
CodeInvalidProof CodeType = 205
|
||||
CodeNotBelowTarget CodeType = 206
|
||||
CodeInvalidCount CodeType = 207
|
||||
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
|
||||
)
|
||||
|
||||
func codeToDefaultMsg(code CodeType) string {
|
||||
switch code {
|
||||
case CodeInvalidDifficulty:
|
||||
return "Insuffient difficulty"
|
||||
case CodeNonexistentDifficulty:
|
||||
return "Nonexistent difficulty"
|
||||
case CodeNonexistentReward:
|
||||
return "Nonexistent reward"
|
||||
case CodeNonexistentCount:
|
||||
return "Nonexistent count"
|
||||
case CodeInvalidProof:
|
||||
return "Invalid proof"
|
||||
case CodeNotBelowTarget:
|
||||
return "Not below target"
|
||||
case CodeInvalidCount:
|
||||
return "Invalid count"
|
||||
case CodeUnknownRequest:
|
||||
return "Unknown request"
|
||||
default:
|
||||
return sdk.CodeToDefaultMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
func ErrInvalidDifficulty(msg string) sdk.Error {
|
||||
return newError(CodeInvalidDifficulty, msg)
|
||||
}
|
||||
|
||||
func ErrNonexistentDifficulty() sdk.Error {
|
||||
return newError(CodeNonexistentDifficulty, "")
|
||||
}
|
||||
|
||||
func ErrNonexistentReward() sdk.Error {
|
||||
return newError(CodeNonexistentReward, "")
|
||||
}
|
||||
|
||||
func ErrNonexistentCount() sdk.Error {
|
||||
return newError(CodeNonexistentCount, "")
|
||||
}
|
||||
|
||||
func ErrInvalidProof(msg string) sdk.Error {
|
||||
return newError(CodeInvalidProof, msg)
|
||||
}
|
||||
|
||||
func ErrNotBelowTarget(msg string) sdk.Error {
|
||||
return newError(CodeNotBelowTarget, msg)
|
||||
}
|
||||
|
||||
func ErrInvalidCount(msg string) sdk.Error {
|
||||
return newError(CodeInvalidCount, msg)
|
||||
}
|
||||
|
||||
func msgOrDefaultMsg(msg string, code CodeType) string {
|
||||
if msg != "" {
|
||||
return msg
|
||||
} else {
|
||||
return codeToDefaultMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
func newError(code CodeType, msg string) sdk.Error {
|
||||
msg = msgOrDefaultMsg(msg, code)
|
||||
return sdk.NewError(code, msg)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// module users must specify coin denomination and reward (constant) per PoW solution
|
||||
type PowConfig struct {
|
||||
Denomination string
|
||||
Reward int64
|
||||
}
|
||||
|
||||
func NewPowConfig(denomination string, reward int64) PowConfig {
|
||||
return PowConfig{denomination, reward}
|
||||
}
|
||||
|
||||
func NewHandler(ck bank.CoinKeeper, pm Mapper, config PowConfig) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case MineMsg:
|
||||
return handleMineMsg(ctx, ck, pm, config, msg)
|
||||
default:
|
||||
errMsg := "Unrecognized pow Msg type: " + reflect.TypeOf(msg).Name()
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMineMsg(ctx sdk.Context, ck bank.CoinKeeper, pm Mapper, config PowConfig, msg MineMsg) sdk.Result {
|
||||
|
||||
// precondition: msg has passed ValidateBasic
|
||||
|
||||
// will this function always be applied atomically?
|
||||
|
||||
lastDifficulty, err := pm.GetLastDifficulty(ctx)
|
||||
if err != nil {
|
||||
return ErrNonexistentDifficulty().Result()
|
||||
}
|
||||
|
||||
newDifficulty := lastDifficulty + 1
|
||||
|
||||
lastCount, err := pm.GetLastCount(ctx)
|
||||
if err != nil {
|
||||
return ErrNonexistentCount().Result()
|
||||
}
|
||||
|
||||
newCount := lastCount + 1
|
||||
|
||||
if msg.Count != newCount {
|
||||
return ErrInvalidCount(fmt.Sprintf("invalid count: was %d, should have been %d", msg.Count, newCount)).Result()
|
||||
}
|
||||
|
||||
if msg.Difficulty != newDifficulty {
|
||||
return ErrInvalidDifficulty(fmt.Sprintf("invalid difficulty: was %d, should have been %d", msg.Difficulty, newDifficulty)).Result()
|
||||
}
|
||||
|
||||
_, ckErr := ck.AddCoins(ctx, msg.Sender, []sdk.Coin{sdk.Coin{config.Denomination, config.Reward}})
|
||||
if ckErr != nil {
|
||||
return ckErr.Result()
|
||||
}
|
||||
|
||||
pm.SetLastDifficulty(ctx, newDifficulty)
|
||||
pm.SetLastCount(ctx, newCount)
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
func TestPowHandler(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
|
||||
am := auth.NewAccountMapper(capKey, &auth.BaseAccount{})
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
|
||||
mapper := NewMapper(capKey)
|
||||
config := NewPowConfig("pow", int64(1))
|
||||
ck := bank.NewCoinKeeper(am)
|
||||
|
||||
handler := NewHandler(ck, mapper, config)
|
||||
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
count := uint64(1)
|
||||
difficulty := uint64(2)
|
||||
nonce, proof := mine(addr, count, difficulty)
|
||||
msg := NewMineMsg(addr, difficulty, count, nonce, proof)
|
||||
|
||||
result := handler(ctx, msg)
|
||||
assert.Equal(t, result, sdk.Result{})
|
||||
|
||||
newDiff, err := mapper.GetLastDifficulty(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, newDiff, uint64(2))
|
||||
|
||||
newCount, err := mapper.GetLastCount(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, newCount, uint64(1))
|
||||
|
||||
// todo assert correct coin change, awaiting https://github.com/cosmos/cosmos-sdk/pull/691
|
||||
|
||||
difficulty = uint64(4)
|
||||
nonce, proof = mine(addr, count, difficulty)
|
||||
msg = NewMineMsg(addr, difficulty, count, nonce, proof)
|
||||
|
||||
result = handler(ctx, msg)
|
||||
assert.NotEqual(t, result, sdk.Result{})
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type Mapper struct {
|
||||
key sdk.StoreKey
|
||||
}
|
||||
|
||||
func NewMapper(key sdk.StoreKey) Mapper {
|
||||
return Mapper{key}
|
||||
}
|
||||
|
||||
var lastDifficultyKey = []byte("lastDifficultyKey")
|
||||
|
||||
func (pm Mapper) GetLastDifficulty(ctx sdk.Context) (uint64, error) {
|
||||
store := ctx.KVStore(pm.key)
|
||||
stored := store.Get(lastDifficultyKey)
|
||||
if stored == nil {
|
||||
// return the default difficulty of 1 if not set
|
||||
// this works OK for this module, but a way to initalize the store (a "genesis block" for the module) might be better in general
|
||||
return uint64(1), nil
|
||||
} else {
|
||||
return strconv.ParseUint(string(stored), 0, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func (pm Mapper) SetLastDifficulty(ctx sdk.Context, diff uint64) {
|
||||
store := ctx.KVStore(pm.key)
|
||||
store.Set(lastDifficultyKey, []byte(strconv.FormatUint(diff, 16)))
|
||||
}
|
||||
|
||||
var countKey = []byte("count")
|
||||
|
||||
func (pm Mapper) GetLastCount(ctx sdk.Context) (uint64, error) {
|
||||
store := ctx.KVStore(pm.key)
|
||||
stored := store.Get(countKey)
|
||||
if stored == nil {
|
||||
return uint64(0), nil
|
||||
} else {
|
||||
return strconv.ParseUint(string(stored), 0, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func (pm Mapper) SetLastCount(ctx sdk.Context, count uint64) {
|
||||
store := ctx.KVStore(pm.key)
|
||||
store.Set(countKey, []byte(strconv.FormatUint(count, 16)))
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// possibly share this kind of setup functionality between module testsuites?
|
||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
|
||||
db := dbm.NewMemDB()
|
||||
capKey := sdk.NewKVStoreKey("capkey")
|
||||
ms := store.NewCommitMultiStore(db)
|
||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
||||
ms.LoadLatestVersion()
|
||||
|
||||
return ms, capKey
|
||||
}
|
||||
|
||||
func TestPowMapperGetSet(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
|
||||
mapper := NewMapper(capKey)
|
||||
|
||||
res, err := mapper.GetLastDifficulty(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, res, uint64(1))
|
||||
|
||||
mapper.SetLastDifficulty(ctx, 2)
|
||||
|
||||
res, err = mapper.GetLastDifficulty(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, res, uint64(2))
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// MineMsg - mine some coins with PoW
|
||||
type MineMsg struct {
|
||||
Sender sdk.Address `json:"sender"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
Count uint64 `json:"count"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Proof []byte `json:"proof"`
|
||||
}
|
||||
|
||||
// NewMineMsg - construct mine message
|
||||
func NewMineMsg(sender sdk.Address, difficulty uint64, count uint64, nonce uint64, proof []byte) MineMsg {
|
||||
return MineMsg{sender, difficulty, count, nonce, proof}
|
||||
}
|
||||
|
||||
func (msg MineMsg) Type() string { return "mine" }
|
||||
func (msg MineMsg) Get(key interface{}) (value interface{}) { return nil }
|
||||
func (msg MineMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.Sender} }
|
||||
func (msg MineMsg) String() string {
|
||||
return fmt.Sprintf("MineMsg{Sender: %v, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof)
|
||||
}
|
||||
|
||||
func (msg MineMsg) ValidateBasic() sdk.Error {
|
||||
// check hash
|
||||
var data []byte
|
||||
// hash must include sender, so no other users can race the tx
|
||||
data = append(data, []byte(msg.Sender)...)
|
||||
countBytes := strconv.FormatUint(msg.Count, 16)
|
||||
// hash must include count so proof-of-work solutions cannot be replayed
|
||||
data = append(data, countBytes...)
|
||||
nonceBytes := strconv.FormatUint(msg.Nonce, 16)
|
||||
data = append(data, nonceBytes...)
|
||||
hash := crypto.Sha256(data)
|
||||
hashHex := make([]byte, hex.EncodedLen(len(hash)))
|
||||
hex.Encode(hashHex, hash)
|
||||
hashHex = hashHex[:16]
|
||||
if !bytes.Equal(hashHex, msg.Proof) {
|
||||
return ErrInvalidProof(fmt.Sprintf("hashHex: %s, proof: %s", hashHex, msg.Proof))
|
||||
}
|
||||
|
||||
// check proof below difficulty
|
||||
// difficulty is linear - 1 = all hashes, 2 = half of hashes, 3 = third of hashes, etc
|
||||
target := math.MaxUint64 / msg.Difficulty
|
||||
hashUint, err := strconv.ParseUint(string(msg.Proof), 16, 64)
|
||||
if err != nil {
|
||||
return ErrInvalidProof(fmt.Sprintf("proof: %s", msg.Proof))
|
||||
}
|
||||
if hashUint >= target {
|
||||
return ErrNotBelowTarget(fmt.Sprintf("hashuint: %d, target: %d", hashUint, target))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg MineMsg) GetSignBytes() []byte {
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func TestNewMineMsg(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
msg := MineMsg{addr, 0, 0, 0, []byte("")}
|
||||
equiv := NewMineMsg(addr, 0, 0, 0, []byte(""))
|
||||
assert.Equal(t, msg, equiv, "%s != %s", msg, equiv)
|
||||
}
|
||||
|
||||
func TestMineMsgType(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
msg := MineMsg{addr, 0, 0, 0, []byte("")}
|
||||
assert.Equal(t, msg.Type(), "mine")
|
||||
}
|
||||
|
||||
func hash(sender sdk.Address, count uint64, nonce uint64) []byte {
|
||||
var bytes []byte
|
||||
bytes = append(bytes, []byte(sender)...)
|
||||
countBytes := strconv.FormatUint(count, 16)
|
||||
bytes = append(bytes, countBytes...)
|
||||
nonceBytes := strconv.FormatUint(nonce, 16)
|
||||
bytes = append(bytes, nonceBytes...)
|
||||
hash := crypto.Sha256(bytes)
|
||||
// uint64, so we just use the first 8 bytes of the hash
|
||||
// this limits the range of possible difficulty values (as compared to uint256), but fine for proof-of-concept
|
||||
ret := make([]byte, hex.EncodedLen(len(hash)))
|
||||
hex.Encode(ret, hash)
|
||||
return ret[:16]
|
||||
}
|
||||
|
||||
func mine(sender sdk.Address, count uint64, difficulty uint64) (uint64, []byte) {
|
||||
target := math.MaxUint64 / difficulty
|
||||
for nonce := uint64(0); ; nonce++ {
|
||||
hash := hash(sender, count, nonce)
|
||||
hashuint, err := strconv.ParseUint(string(hash), 16, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if hashuint < target {
|
||||
return nonce, hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMineMsgValidation(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
otherAddr := sdk.Address([]byte("another"))
|
||||
count := uint64(0)
|
||||
for difficulty := uint64(1); difficulty < 1000; difficulty += 100 {
|
||||
count += 1
|
||||
nonce, proof := mine(addr, count, difficulty)
|
||||
msg := MineMsg{addr, difficulty, count, nonce, proof}
|
||||
err := msg.ValidateBasic()
|
||||
assert.Nil(t, err, "error with difficulty %d - %+v", difficulty, err)
|
||||
|
||||
msg.Count += 1
|
||||
err = msg.ValidateBasic()
|
||||
assert.NotNil(t, err, "count was wrong, should have thrown error with msg %s", msg)
|
||||
|
||||
msg.Count -= 1
|
||||
msg.Nonce += 1
|
||||
err = msg.ValidateBasic()
|
||||
assert.NotNil(t, err, "nonce was wrong, should have thrown error with msg %s", msg)
|
||||
|
||||
msg.Nonce -= 1
|
||||
msg.Sender = otherAddr
|
||||
err = msg.ValidateBasic()
|
||||
assert.NotNil(t, err, "sender was wrong, should have thrown error with msg %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMineMsgString(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
msg := MineMsg{addr, 0, 0, 0, []byte("abc")}
|
||||
res := msg.String()
|
||||
assert.Equal(t, res, "MineMsg{Sender: 73656E646572, Difficulty: 0, Count: 0, Nonce: 0, Proof: abc}")
|
||||
}
|
||||
|
||||
func TestMineMsgGet(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
msg := MineMsg{addr, 0, 0, 0, []byte("")}
|
||||
res := msg.Get(nil)
|
||||
assert.Nil(t, res)
|
||||
}
|
||||
|
||||
func TestMineMsgGetSignBytes(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
msg := MineMsg{addr, 1, 1, 1, []byte("abc")}
|
||||
res := msg.GetSignBytes()
|
||||
assert.Equal(t, string(res), `{"sender":"73656E646572","difficulty":1,"count":1,"nonce":1,"proof":"YWJj"}`)
|
||||
}
|
||||
|
||||
func TestMineMsgGetSigners(t *testing.T) {
|
||||
addr := sdk.Address([]byte("sender"))
|
||||
msg := MineMsg{addr, 1, 1, 1, []byte("abc")}
|
||||
res := msg.GetSigners()
|
||||
assert.Equal(t, fmt.Sprintf("%v", res), "[73656E646572]")
|
||||
}
|
||||
Reference in New Issue
Block a user