Skip to content

Commit

Permalink
Validator scenario (#287)
Browse files Browse the repository at this point in the history
* Update validator example

* Update validator scenario with multiSig

* Review comments; extract admin client

* Move multisig initializer to first position
  • Loading branch information
alpe authored Jan 23, 2019
1 parent c25d96a commit fefebb1
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 48 deletions.
7 changes: 5 additions & 2 deletions cmd/bnsd/app/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/iov-one/weave/x/multisig"
"path/filepath"

"github.com/iov-one/weave/x/validators"

"github.com/iov-one/weave"
"github.com/iov-one/weave/app"
"github.com/iov-one/weave/cmd/bnsd/x/nft/blockchain"
Expand All @@ -14,7 +17,6 @@ import (
"github.com/iov-one/weave/x"
"github.com/iov-one/weave/x/cash"
"github.com/iov-one/weave/x/currency"
"github.com/iov-one/weave/x/multisig"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
)
Expand Down Expand Up @@ -81,11 +83,12 @@ func GenerateApp(home string, logger log.Logger, debug bool) (abci.Application,
return nil, err
}
application.WithInit(app.ChainInitializers(
&multisig.Initializer{},
&cash.Initializer{},
&currency.Initializer{},
&blockchain.Initializer{},
&ticker.Initializer{},
&multisig.Initializer{},
&validators.Initializer{},
))

// set the logger and return
Expand Down
27 changes: 27 additions & 0 deletions cmd/bnsd/client/admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package client

import (
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)

const (
CurrentHeight int64 = -1
)

// Admin encapsulates operational functions to interact with the cluster.
type admin struct {
*BnsClient
}

func Admin(c *BnsClient) *admin {
return &admin{BnsClient: c}
}

// GetValidators returns the validator set for the given height. Height can be `negative` to return them for the latest block.
func (c *admin) GetValidators(height int64) (*ctypes.ResultValidators, error) {
v := &height
if height < 0 {
v = nil
}
return c.BnsClient.conn.Validators(v)
}
12 changes: 12 additions & 0 deletions cmd/bnsd/client/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/iov-one/weave/x"
"github.com/iov-one/weave/x/cash"
"github.com/iov-one/weave/x/sigs"
"github.com/iov-one/weave/x/validators"
)

// Tx is all the interfaces we need rolled into one
Expand Down Expand Up @@ -46,3 +47,14 @@ func ParseBcpTx(data []byte) (*app.Tx, error) {
}
return &tx, nil
}

// SetValidatorTx will create an unsigned tx to replace current validator set
func SetValidatorTx(u ...*validators.ValidatorUpdate) *app.Tx {
return &app.Tx{
Sum: &app.Tx_SetValidatorsMsg{
SetValidatorsMsg: &validators.SetValidatorsMsg{
ValidatorUpdates: u,
},
},
}
}
36 changes: 29 additions & 7 deletions cmd/bnsd/scenarios/main_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package scenarios

import (
"encoding/binary"
"flag"
"fmt"
"os"
"strings"
"testing"
"time"

"github.com/iov-one/weave/x/multisig"

"github.com/iov-one/weave"
"github.com/iov-one/weave/cmd/bnsd/app"
"github.com/iov-one/weave/cmd/bnsd/client"
Expand All @@ -32,15 +36,20 @@ var (
)

var (
alice *client.PrivateKey
node *nm.Node
logger = log.NewTMLogger(os.Stdout) //log.NewNopLogger()
bnsClient *client.BnsClient
chainID string
alice *client.PrivateKey
node *nm.Node
logger = log.NewTMLogger(os.Stdout) //log.NewNopLogger()
bnsClient *client.BnsClient
chainID string
rpcAddress string
multiSigContractID = make([]byte, 8) // first contractID
multiSigContractAddr weave.Address // results to: "5AE2C58796B0AD48FFE7602EAC3353488C859A2B"
)

func TestMain(m *testing.M) {
flag.Parse()
binary.BigEndian.PutUint64(multiSigContractID, 1)
multiSigContractAddr = multisig.MultiSigCondition(multiSigContractID).Address()
var err error
alice, err = client.DecodePrivateKey(*hexSeed)
if err != nil {
Expand All @@ -55,12 +64,15 @@ func TestMain(m *testing.M) {
logger.Error("Failed to fetch chain id", "cause", err)
os.Exit(1)
}
rpcAddress = *tendermintAddress
os.Exit(m.Run())
}

config := rpctest.GetConfig()
config.Moniker = "SetInTestMain"
chainID = config.ChainID()

rpcAddress = "http://localhost" + config.RPC.ListenAddress[strings.LastIndex(config.RPC.ListenAddress, ":"):]
app, err := initApp(config, alice.PublicKey().Address())
if err != nil {
logger.Error("Failed to init app", "cause", err)
Expand Down Expand Up @@ -126,9 +138,19 @@ func initGenesis(filename string, addr weave.Address) (*tm.GenesisDoc, error) {
}
]
}
]
],
"update_validators": {
"addresses": ["%s"]
},
"multisig": [
{
"sigs": ["%s"],
"activation_threshold": 1,
"admin_threshold": 1
}
]
}
`, addr)
`, addr, multiSigContractAddr, addr)
doc.AppState = []byte(appState)
// save file
return doc, doc.SaveAs(filename)
Expand Down
100 changes: 100 additions & 0 deletions cmd/bnsd/scenarios/validator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package scenarios

import (
"testing"
"time"

"github.com/iov-one/weave/cmd/bnsd/client"
"github.com/iov-one/weave/x/validators"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/types"
)

func TestUpdateValidatorSet(t *testing.T) {
current, err := client.Admin(bnsClient).GetValidators(client.CurrentHeight)
require.NoError(t, err)

newValidator := ed25519.GenPrivKey()
keyEd25519 := newValidator.PubKey().(ed25519.PubKeyEd25519)
aNonce := client.NewNonce(bnsClient, alice.PublicKey().Address())

// when adding a new validator
addValidatorTX := client.SetValidatorTx(
&validators.ValidatorUpdate{
Pubkey: validators.Pubkey{
Type: "ed25519",
Data: keyEd25519[:],
},
Power: 1,
},
)
addValidatorTX.Multisig = [][]byte{multiSigContractID}

seq, err := aNonce.Next()
require.NoError(t, err)
require.NoError(t, client.SignTx(addValidatorTX, alice, chainID, seq))
resp := bnsClient.BroadcastTx(addValidatorTX)

// then
require.NoError(t, resp.IsError())
t.Logf("Added validator: %X\n", keyEd25519)

// and tendermint validator set is updated
tmValidatorSet := awaitValidatorUpdate(resp.Response.Height + 2)
require.NotNil(t, tmValidatorSet)
require.Len(t, tmValidatorSet.Validators, len(current.Validators)+1)
require.True(t, contains(tmValidatorSet.Validators, newValidator.PubKey()))

// and when delete validator
delValidatorTX := client.SetValidatorTx(
&validators.ValidatorUpdate{
Pubkey: validators.Pubkey{
Type: "ed25519",
Data: keyEd25519[:],
},
Power: 0, // 0 for delete
},
)
delValidatorTX.Multisig = [][]byte{multiSigContractID}

// then
seq, err = aNonce.Next()
require.NoError(t, err)
require.NoError(t, client.SignTx(delValidatorTX, alice, chainID, seq))
resp = bnsClient.BroadcastTx(delValidatorTX)

// then
require.NoError(t, resp.IsError())
t.Logf("Removed validator: %X\n", keyEd25519)

// and tendermint validator set is updated
tmValidatorSet = awaitValidatorUpdate(resp.Response.Height + 2)
require.NotNil(t, tmValidatorSet)
require.Len(t, tmValidatorSet.Validators, len(current.Validators))
assert.False(t, contains(tmValidatorSet.Validators, newValidator.PubKey()))
}

func awaitValidatorUpdate(height int64) *ctypes.ResultValidators {
admin := client.Admin(bnsClient)
for i := 0; i < 15; i++ {
v, err := admin.GetValidators(height)
if err == nil {
return v
}
time.Sleep(time.Duration(i) * 50 * time.Millisecond)
}
return nil
}

func contains(got []*types.Validator, exp crypto.PubKey) bool {
for _, v := range got {
if exp.Equals(v.PubKey.(ed25519.PubKeyEd25519)) {
return true
}
}
return false
}
10 changes: 7 additions & 3 deletions cmd/bnsd/scenarios/wallet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ func TestSendTokens(t *testing.T) {
emilia := client.GenPrivateKey()
heights := make([]int64, 4)
aNonce := client.NewNonce(bnsClient, alice.PublicKey().Address())
for i, ticker := range []string{"IOV", "CASH", "ALX", "PAJA"} {

walletResp, err := bnsClient.GetWallet(alice.PublicKey().Address())
require.NotEmpty(t, walletResp.Wallet.Coins)
for i, coin := range walletResp.Wallet.Coins {
// send a coin from Alice to Emilia
coin := x.Coin{
Ticker: ticker,
Ticker: coin.Ticker,
Fractional: 0,
Whole: 1,
}
Expand All @@ -28,7 +32,7 @@ func TestSendTokens(t *testing.T) {
heights[i] = resp.Response.Height
delayForRateLimits()
}
walletResp, err := bnsClient.GetWallet(emilia.PublicKey().Address())
walletResp, err = bnsClient.GetWallet(emilia.PublicKey().Address())
require.NoError(t, err)
t.Log("message", "done", "height", heights, "coins", walletResp.Wallet.Coins)
}
16 changes: 11 additions & 5 deletions x/validators/errors.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package validators

import (
"fmt"
stderr "errors"
"reflect"

"github.com/iov-one/weave/errors"
Expand All @@ -10,13 +10,19 @@ import (
// ABCI Response Codes
// x/update_validators reserves 40 ~ 49.
const (
CodeEmptyDiff uint32 = 40
CodeWrongType = 41
CodeEmptyDiff = 40
CodeWrongType = 41
CodeInvalidPubKey = 42
CodeEmptyValidatorSet = 43
CodeInvalidPower = 44
)

var (
errEmptyDiff = fmt.Errorf("Empty validator diff")
errWrongType = fmt.Errorf("Wrong type for accounts storage")
errEmptyDiff = stderr.New("Empty validator diff")
errWrongType = stderr.New("Wrong type for accounts storage")
errInvalidPubKey = stderr.New("Invalid public key")
errEmptyValidatorSet = stderr.New("Empty validator set")
errInvalidPower = stderr.New("Power value is invalid")
)

func ErrEmptyDiff() error {
Expand Down
42 changes: 17 additions & 25 deletions x/validators/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/iov-one/weave"
"github.com/iov-one/weave/errors"
"github.com/iov-one/weave/x"
abci "github.com/tendermint/tendermint/abci/types"
)

type AuthCheckAddress = func(auth x.Authenticator, ctx weave.Context) CheckAddress
Expand Down Expand Up @@ -48,47 +49,38 @@ func NewUpdateHandler(auth x.Authenticator, control Controller, checkAddr AuthCh
// Check verifies all the preconditions
func (h UpdateHandler) Check(ctx weave.Context, store weave.KVStore,
tx weave.Tx) (weave.CheckResult, error) {

var res weave.CheckResult
rmsg, err := tx.GetMsg()
if err != nil {
return res, err
}
msg, ok := rmsg.(*SetValidatorsMsg)
if !ok {
return res, errors.ErrUnknownTxType(rmsg)
}

_, err = h.control.CanUpdateValidators(store, h.authCheckAddress(h.auth, ctx), msg.AsABCI())
if err != nil {
return res, err
}

return res, nil
_, err := h.validate(ctx, store, tx)
return res, err
}

// Deliver provides the diff given everything is okay with permissions and such
// Check did the same job already, so we can assume stuff goes okay
func (h UpdateHandler) Deliver(ctx weave.Context, store weave.KVStore,
tx weave.Tx) (weave.DeliverResult, error) {

// ensure type and validate...
var res weave.DeliverResult
rmsg, err := tx.GetMsg()
diff, err := h.validate(ctx, store, tx)
if err != nil {
return res, err
}
res.Diff = diff
return res, nil
}

func (h UpdateHandler) validate(ctx weave.Context, store weave.KVStore, tx weave.Tx) ([]abci.ValidatorUpdate, error) {
rmsg, err := tx.GetMsg()
if err != nil {
return nil, err
}
msg, ok := rmsg.(*SetValidatorsMsg)
if !ok {
return res, errors.ErrUnknownTxType(rmsg)
return nil, errors.ErrUnknownTxType(rmsg)
}

diff, err := h.control.CanUpdateValidators(store, h.authCheckAddress(h.auth, ctx), msg.AsABCI())
err = msg.Validate()
if err != nil {
return res, err
return nil, err
}

res.Diff = diff

return res, nil
return h.control.CanUpdateValidators(store, h.authCheckAddress(h.auth, ctx), msg.AsABCI())
}
Loading

0 comments on commit fefebb1

Please sign in to comment.