Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

go-kosu: ethereum snapshot block #270

Merged
merged 6 commits into from Sep 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/go-kosu/CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

## master

- Ethereum Snapshot block

## v0.3.0

- introduce validators verification from app_state
Expand Down
71 changes: 71 additions & 0 deletions packages/go-kosu/abci/app_test.go
Expand Up @@ -131,3 +131,74 @@ func TestUpdateConfirmationThreshold(t *testing.T) {
)
}
}

// nolint:lll
func TestGenesisStateCorrectness(t *testing.T) {
dir := initTendermint(t)
closer := func() { _ = os.RemoveAll(dir) }
defer closer()

app := NewApp(db.NewMemDB(), dir)

update := abci.Ed25519ValidatorUpdate([]byte("some_pub_key"), 10)
updates := abci.ValidatorUpdates{update}

/*
9339CD2572AB19E2A2E431EEF2E9FD2B1A91C472 is the Address of the update
To retrieve it call GetUpdateAddress(&update)
*/

t.Run("InitialValidatorInfo_And_Snapshot_Defined", func(t *testing.T) {
app.InitChain(abci.RequestInitChain{
Validators: updates, AppStateBytes: []byte(`{
"initial_validator_info": [
{"tendermint_address": "9339CD2572AB19E2A2E431EEF2E9FD2B1A91C472", "ethereum_address": "0xethereum", "initial_stake": "10000000000000000000"}
],
"snapshot_block": 999
}`),
})
})

t.Run("InitialValidatorInfo_And_Snapshot_Zero", func(t *testing.T) {
fn := func() {
app.InitChain(abci.RequestInitChain{
Validators: updates, AppStateBytes: []byte(`{
"initial_validator_info": [
{"tendermint_address": "9339CD2572AB19E2A2E431EEF2E9FD2B1A91C472", "ethereum_address": "0xethereum", "initial_stake": "10000000000000000000"}
],
"snapshot_block": 0
}`),
})
}
assert.Panics(t, fn)
})

t.Run("Balances_Doesnt_Match", func(t *testing.T) {
fn := func() {
app.InitChain(abci.RequestInitChain{
Validators: updates, AppStateBytes: []byte(`{
"initial_validator_info": [
{"tendermint_address": "9339CD2572AB19E2A2E431EEF2E9FD2B1A91C472", "ethereum_address": "0xethereum", "initial_stake": "99990000000000000000"}
],
"snapshot_block": 999
}`),
})
}
assert.Panics(t, fn)
})

t.Run("PublicKeys_Doesnt_Match", func(t *testing.T) {
fn := func() {
app.InitChain(abci.RequestInitChain{
Validators: updates, AppStateBytes: []byte(`{
"initial_validator_info": [
{"tendermint_address": "0000000000000000000000000000000000000000", "ethereum_address": "0xethereum", "initial_stake": "10000000000000000000"}
],
"snapshot_block": 999
}`),
})
}
assert.Panics(t, fn)
})

}
17 changes: 12 additions & 5 deletions packages/go-kosu/abci/genesis.go
@@ -1,8 +1,8 @@
package abci

import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"

abci "github.com/tendermint/tendermint/abci/types"
Expand All @@ -13,24 +13,26 @@ import (
// GenesisValidator is the data structure used to define a validator in the app_state section of the genesis file
// It links a Tendermint PublicKey with an Ethereum Address.
type GenesisValidator struct {
PublicKey []byte
EthereumAddress string
InitialStake string
TendermintAddress string `json:"tendermint_address"`
EthereumAddress string `json:"ethereum_address"`
InitialStake string `json:"initial_stake"`
}

// GenesisValidatorSet is the initial set of validators
type GenesisValidatorSet []GenesisValidator

func (s GenesisValidatorSet) Len() int { return len(s) }
func (s GenesisValidatorSet) Less(i, j int) bool {
return bytes.Compare(s[i].PublicKey, s[j].PublicKey) <= 0
return s[i].TendermintAddress < s[j].TendermintAddress
}
func (s GenesisValidatorSet) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

// Genesis is the initial chain state
type Genesis struct {
ConsensusParams types.ConsensusParams `json:"consensus_params"`
InitialValidatorInfo GenesisValidatorSet `json:"initial_validator_info"`
// SnapshotBlock indicates the first Ethereum block we will subscribe to
SnapshotBlock uint64 `json:"snapshot_block"`
}

// NewGenesisFromRequest returnsa a new Genesis object given a RequestInitChain
Expand All @@ -42,6 +44,10 @@ func NewGenesisFromRequest(req abci.RequestInitChain) (*Genesis, error) {
if err := json.Unmarshal(req.AppStateBytes, gen); err != nil {
return nil, err
}

if len(gen.InitialValidatorInfo) != 0 && gen.SnapshotBlock == 0 {
return nil, errors.New(".SnapshotBlock can't be zero when .InitialValidatorInfo are defined")
}
return gen, nil
}

Expand Down Expand Up @@ -79,4 +85,5 @@ var GenesisAppState = &Genesis{
BlocksBeforePruning: 10,
},
InitialValidatorInfo: nil,
SnapshotBlock: 0,
}
31 changes: 21 additions & 10 deletions packages/go-kosu/abci/validators.go
@@ -1,14 +1,16 @@
package abci

import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"math/big"
"sort"
"strings"

"github.com/ParadigmFoundation/kosu-monorepo/packages/go-kosu/abci/types"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/tmhash"
)

var (
Expand All @@ -17,16 +19,19 @@ var (
// ErrLengthMismatch means that the validators set sizes are not equal
ErrLengthMismatch = errors.New("length mismatch")

// NewPublicKeyMismatchError returns a PublicKey Mismatch formatted error
NewPublicKeyMismatchError = func(pk1, pk2 []byte) error {
return fmt.Errorf("public key '%X' != '%X'", pk1, pk2)
}
// NewBalanceMismatchError returns a Balance Mismatch formatted error
NewBalanceMismatchError = func(b1, b2 int64) error {
return fmt.Errorf("balance %d != %d", b1, b2)
}
)

// GetUpdateAddress returns the Tendermint's address of a Validator Update
func GetUpdateAddress(update *abci.ValidatorUpdate) string {
key := tmhash.SumTruncated(update.PubKey.Data)
enc := hex.EncodeToString(key)
return strings.ToUpper(enc)
}

// UnifyValidators returns a new validator set built out of ValidatorUpdates and GenesisValidatorSet,
// both present in the genesis file.
// We use the ValidatorUpdates to get the validator node info,
Expand Down Expand Up @@ -61,15 +66,21 @@ func UnifyValidators(updates abci.ValidatorUpdates, state GenesisValidatorSet) (
return nil, ErrLengthMismatch
}

// create a map of validators addresses to verify if they exist
addresses := make(map[string]struct{})
for _, update := range updates {
addr := GetUpdateAddress(&update)
addresses[addr] = struct{}{}
}

for i := range newSet {
v := &newSet[i]
v := newSet[i]
s := state[i]

// key verification
if !bytes.Equal(v.PublicKey, s.PublicKey) {
return nil, NewPublicKeyMismatchError(
v.PublicKey, s.PublicKey,
)
if _, ok := addresses[s.TendermintAddress]; !ok {
err := fmt.Errorf("address from app_state.initial_validator_info[%s] was not found in []validators", s.TendermintAddress) // nolint
return nil, err
}

// balance verification
Expand Down
58 changes: 3 additions & 55 deletions packages/go-kosu/abci/validators_test.go
Expand Up @@ -53,65 +53,13 @@ func TestValidatorsVerification(t *testing.T) {
}

gen := GenesisValidatorSet{
GenesisValidator{PublicKey: pk2, EthereumAddress: "0x1", InitialStake: "1"},
GenesisValidator{TendermintAddress: string(pk2), EthereumAddress: "0x1", InitialStake: "1"},
}

_, err := UnifyValidators(updates, gen)
require.Equal(t, NewPublicKeyMismatchError(pk1, pk2), err)
require.Error(t, err)

})

t.Run("Balance Mismatch", func(t *testing.T) {
pk := []byte("pk")

updates := abci.ValidatorUpdates{
abci.Ed25519ValidatorUpdate(pk, 1),
}

gen := GenesisValidatorSet{
GenesisValidator{PublicKey: pk, EthereumAddress: "0x1", InitialStake: "20000000000000000000"},
}

_, err := UnifyValidators(updates, gen)
require.Equal(t, NewBalanceMismatchError(1, 20), err)

})

t.Run("MatchingOK", func(t *testing.T) {
pk1 := []byte("abc")
pk2 := []byte("xxx")

updates := abci.ValidatorUpdates{
abci.Ed25519ValidatorUpdate(pk1, 1),
abci.Ed25519ValidatorUpdate(pk2, 2),
}

gen := GenesisValidatorSet{
GenesisValidator{PublicKey: pk2, EthereumAddress: "0x2", InitialStake: "2000000000000000000"},
GenesisValidator{PublicKey: pk1, EthereumAddress: "0x1", InitialStake: "1000000000000000000"},
}

set, err := UnifyValidators(updates, gen)
require.NoError(t, err)
require.Len(t, set, len(updates))

expectedValidators := []struct {
EthAccount string
Balance string
Power int64
PublicKey []byte
}{
{"0x1", "1000000000000000000", 1, pk1},
{"0x2", "2000000000000000000", 2, pk2},
}

for i, v := range expectedValidators {
assert.Equal(t, v.EthAccount, set[i].EthAccount)
assert.Equal(t, v.Balance, set[i].Balance.BigInt().String())
assert.Equal(t, v.Power, set[i].Power)
assert.Equal(t, v.PublicKey, set[i].PublicKey)
}
})
}

func TestValidatorsVerificationOnInitChain(t *testing.T) {
Expand All @@ -125,7 +73,7 @@ func TestValidatorsVerificationOnInitChain(t *testing.T) {
require.NoError(t, json.Unmarshal(doc.AppState, gen))

gen.InitialValidatorInfo = GenesisValidatorSet{
GenesisValidator{PublicKey: []byte("key_foo")},
GenesisValidator{TendermintAddress: "key_foo"},
}

assert.Panics(t, func() {
Expand Down
7 changes: 5 additions & 2 deletions packages/go-kosu/cmd/kosud/main.go
Expand Up @@ -47,12 +47,15 @@ func startWitness(ctx context.Context, app *abci.App, ethAddr string, logger log
return err
}

p, err := witness.NewEthereumProvider(ethAddr)
gen, err := abci.NewGenesisFromFile(app.Config.GenesisFile())
if err != nil {
return err
}

gen, err := abci.NewGenesisFromFile(app.Config.GenesisFile())
ethOpts := witness.EthereumProviderOpts{
SnapshotBlock: gen.SnapshotBlock,
}
p, err := witness.NewEthereumProviderWithOpts(ethAddr, ethOpts)
if err != nil {
return err
}
Expand Down
15 changes: 11 additions & 4 deletions packages/go-kosu/witness/eth.go
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
Expand All @@ -16,9 +17,10 @@ var _ Provider = &EthereumProvider{}

// EthereumProviderOpts controls internal provider options
type EthereumProviderOpts struct {
Delay time.Duration
DelayFactor int
DelayMax time.Duration
Delay time.Duration
DelayFactor int
DelayMax time.Duration
SnapshotBlock uint64
}

// DefaultEthereumProviderOpts provides sensible options for the provider
Expand Down Expand Up @@ -95,6 +97,7 @@ func (w *EthereumProvider) backoff(name string, fn func() error) error {
// WatchEvents watches for emitted events from the EventEmitter contract.
// First, emits the existing events in the blockchain, then subscribes to get the emitted events as they happen
func (w *EthereumProvider) WatchEvents(ctx context.Context, fn EventHandler) error {
w.log.Info("Watching for Events", "snapshot", w.opts.SnapshotBlock)
return w.backoff("events", func() error {
return w.watchEvents(ctx, fn)
})
Expand All @@ -109,7 +112,11 @@ func (w *EthereumProvider) watchEvents(ctx context.Context, fn EventHandler) err
return err
}

f, err := emitter.FilterKosuEvent(nil)
// Get events from SnapshotBlock
fOpts := &bind.FilterOpts{
Start: w.opts.SnapshotBlock,
}
f, err := emitter.FilterKosuEvent(fOpts)
if err != nil {
return err
}
Expand Down