diff --git a/packages/go-kosu/abci/app.go b/packages/go-kosu/abci/app.go index f6a01b7d..83c17f01 100644 --- a/packages/go-kosu/abci/app.go +++ b/packages/go-kosu/abci/app.go @@ -141,8 +141,6 @@ func (app *App) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { v = app.store.Validator(nodeID) } - v.Power = vote.Validator.Power - if vote.SignedLastBlock { v.Active = true v.TotalVotes++ @@ -208,8 +206,12 @@ func (app *App) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { updates = append(updates, update) } - v.Applied = true - app.store.SetValidator(nodeID, v) + if v.Power == 0 { + app.store.DeleteValidator(nodeID) + } else { + v.Applied = true + app.store.SetValidator(nodeID, v) + } }) return abci.ResponseEndBlock{ diff --git a/packages/go-kosu/abci/cli/cli.go b/packages/go-kosu/abci/cli/cli.go index 0e8bccda..f7a8f085 100644 --- a/packages/go-kosu/abci/cli/cli.go +++ b/packages/go-kosu/abci/cli/cli.go @@ -1,6 +1,8 @@ package cli import ( + "context" + "encoding/base64" "fmt" "go-kosu/abci" "go-kosu/abci/types" @@ -56,7 +58,7 @@ func (cli *CLI) RebalanceTx() *cobra.Command { } if res.DeliverTx.IsErr() { - printAndExit("deliver tx: %s\n", res.DeliverTx.Log) + printAndExit("deliver tx: %s\n", res.DeliverTx.Info) } fmt.Printf("ok: < %s>\n", tx.RoundInfo) @@ -65,6 +67,70 @@ func (cli *CLI) RebalanceTx() *cobra.Command { } } +// UpdateValidators updates the validator set by sending a Validator's WitnessTx +func (cli *CLI) UpdateValidators() *cobra.Command { + return &cobra.Command{ + Use: "update-validator
", + Short: "update validators by sending a WitnessTx", + Long: "pubkey needs to be encoded in base64. To remove a validator use power = 0", + Args: cobra.ExactArgs(4), + RunE: func(cmd *cobra.Command, args []string) error { + block, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + pubKey, err := base64.StdEncoding.DecodeString(args[1]) + if err != nil { + return err + } + + addr, amount := args[2], args[3] + + tx := &types.TransactionWitness{ + Subject: types.TransactionWitness_VALIDATOR, + Block: block, + PublicKey: pubKey, + Address: addr, + Amount: types.NewBigIntFromString(amount, 10), + } + + res, err := cli.client.BroadcastTxCommit(tx) + if err != nil { + printAndExit("%v\n", err) + } + + if res.CheckTx.IsErr() { + printAndExit("check tx: %s\n", res.CheckTx.Log) + } + + if res.DeliverTx.IsErr() { + printAndExit("deliver tx: %s\n", res.DeliverTx.Info) + } + + ch, closer, err := cli.client.Subscribe(context.Background(), "tm.event = 'NewBlock'") + if err != nil { + printAndExit("subscribe: %v", err) + } + defer closer() + + // wait for the next block + <-ch + + set, err := cli.client.Validators(nil) + if err != nil { + printAndExit("validators: %v", err) + } + + fmt.Println("New validator set") + for _, v := range set.Validators { + fmt.Printf("<%s power:%d>\n", v.PubKey.Address().String(), v.VotingPower) + } + return nil + }, + } +} + // QueryRoundInfo queries the round info func (cli *CLI) QueryRoundInfo() *cobra.Command { return &cobra.Command{ diff --git a/packages/go-kosu/abci/client.go b/packages/go-kosu/abci/client.go index 30e32631..7b82f956 100644 --- a/packages/go-kosu/abci/client.go +++ b/packages/go-kosu/abci/client.go @@ -96,7 +96,6 @@ func (c *Client) Subscribe(ctx context.Context, q string) (<-chan rpctypes.Resul if err := httpC.Start(); err != nil { return nil, nil, err } - } } diff --git a/packages/go-kosu/abci/types/types.go b/packages/go-kosu/abci/types/types.go index cdc0b57f..0f7d0168 100644 --- a/packages/go-kosu/abci/types/types.go +++ b/packages/go-kosu/abci/types/types.go @@ -25,6 +25,11 @@ func (b *BigInt) MarshalText() ([]byte, error) { return []byte(str), nil } +// Zero returns true if the value is 0 +func (b *BigInt) Zero() bool { + return b.BigInt().Cmp(big.NewInt(0)) == 0 +} + var ( // ErrNoPubKeyExpected is returned when no public key was expected but it was defined ErrNoPubKeyExpected = errors.New("expected no publicKey for poster witnesses") diff --git a/packages/go-kosu/abci/witness.go b/packages/go-kosu/abci/witness.go index a5656aaa..b5d88133 100644 --- a/packages/go-kosu/abci/witness.go +++ b/packages/go-kosu/abci/witness.go @@ -9,10 +9,11 @@ import ( "go-kosu/store" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/tmhash" ) func (app *App) checkWitnessTx(tx *types.TransactionWitness) error { - if app.store.LastEvent() >= tx.Block { + if app.store.LastEvent() > tx.Block { return errors.New("transaction is older than the recorded state") } @@ -47,8 +48,6 @@ func (app *App) pushTransactionWitness(tx *types.TransactionWitness, nodeID []by }) } - // TODO(gchaincl): delete witness - wTx := app.store.WitnessTx(tx.Id) v := app.store.Validator(nodeID) @@ -58,38 +57,31 @@ func (app *App) pushTransactionWitness(tx *types.TransactionWitness, nodeID []by return err } - power := scaleBalance(tx.Amount.BigInt()) - app.log.Info("adding confirmations", "+power", power, "current", wTx.Confirmations) - wTx.Confirmations += uint64(power) + app.log.Info("adding confirmations", "+power", v.Power, "current", wTx.Confirmations) + wTx.Confirmations += uint64(v.Power) app.store.SetWitnessTx(wTx) - app.log.Error("info", "threshold", app.confirmationThreshold, "conf", wTx.Confirmations) + app.log.Info("info", "threshold", app.confirmationThreshold, "conf", wTx.Confirmations) if app.confirmationThreshold > wTx.Confirmations { return nil } - - if tx.Amount.BigInt().Uint64() == 0 { - switch tx.Subject { - case types.TransactionWitness_POSTER: - app.store.DeletePoster(tx.Address) - case types.TransactionWitness_VALIDATOR: - app.store.DeleteValidator(nodeID) - } - return nil - } + // app.store.DeleteWitnessTx(tx.Id) switch tx.Subject { case types.TransactionWitness_POSTER: - app.store.SetPoster(tx.Address, types.Poster{ - Balance: tx.Amount, - }) + if tx.Amount.Zero() { + app.store.DeletePoster(tx.Address) + } else { + app.store.SetPoster(tx.Address, types.Poster{Balance: tx.Amount}) + } case types.TransactionWitness_VALIDATOR: - app.store.SetValidator(nodeID, &types.Validator{ + id := tmhash.SumTruncated(tx.PublicKey) + app.store.SetValidator(id, &types.Validator{ Balance: tx.Amount, + Power: scaleBalance(tx.Amount.BigInt()), PublicKey: tx.PublicKey, EthAccount: tx.Address, - Active: false, - Power: power, + Applied: false, }) } return nil diff --git a/packages/go-kosu/cmd/kosu-cli/main.go b/packages/go-kosu/cmd/kosu-cli/main.go index 2de58c8f..b27635f4 100644 --- a/packages/go-kosu/cmd/kosu-cli/main.go +++ b/packages/go-kosu/cmd/kosu-cli/main.go @@ -57,6 +57,7 @@ func main() { } tx.AddCommand( abci.RebalanceTx(), + abci.UpdateValidators(), ) query := &cobra.Command{ diff --git a/packages/go-kosu/store/cosmos/store.go b/packages/go-kosu/store/cosmos/store.go index 392f0895..c37f6f75 100644 --- a/packages/go-kosu/store/cosmos/store.go +++ b/packages/go-kosu/store/cosmos/store.go @@ -164,6 +164,12 @@ func (s *Store) SetWitnessTx(tx store.TransactionWitness) { s.Set("confs:"+string(tx.Id), s.witnessKey, tx.Confirmations) } +// DeleteWitnessTx deletes a WitnessTx +func (s *Store) DeleteWitnessTx(id []byte) { + s.Delete("proto:"+string(id), s.witnessKey) + s.Delete("confs:"+string(id), s.witnessKey) +} + // Poster gets a Poster func (s *Store) Poster(addr string) *types.Poster { var v types.Poster diff --git a/packages/go-kosu/store/store.go b/packages/go-kosu/store/store.go index 9d0fb7be..12ad70b2 100644 --- a/packages/go-kosu/store/store.go +++ b/packages/go-kosu/store/store.go @@ -26,6 +26,7 @@ type Store interface { WitnessTxExists([]byte) bool WitnessTx([]byte) TransactionWitness SetWitnessTx(TransactionWitness) + DeleteWitnessTx([]byte) Poster(string) *types.Poster SetPoster(string, types.Poster) diff --git a/packages/go-kosu/store/storetest/testing.go b/packages/go-kosu/store/storetest/testing.go index a9f59e38..1302ac13 100644 --- a/packages/go-kosu/store/storetest/testing.go +++ b/packages/go-kosu/store/storetest/testing.go @@ -99,6 +99,7 @@ func TestValidator(t *testing.T, s store.Store) { addr := []byte{0, 1, 2, 3, 4} v := &types.Validator{ PublicKey: []byte{0, 1, 2, 3, 4}, + Power: 10, } s.SetValidator(addr, v)