Remove incentivisation service
Incentivisation was an experiment in running an incentivised fleet that rewarded nodes based on their well behavior. It was heavily influenced by https://docs.loki.network/ . It is currently not used anymore, so removing.
This commit is contained in:
parent
2d17c40631
commit
d5086d6e89
|
@ -3,7 +3,6 @@
|
||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -14,11 +13,9 @@ import (
|
||||||
"github.com/syndtr/goleveldb/leveldb"
|
"github.com/syndtr/goleveldb/leveldb"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
"github.com/ethereum/go-ethereum/les"
|
"github.com/ethereum/go-ethereum/les"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
@ -33,7 +30,6 @@ import (
|
||||||
"github.com/status-im/status-go/mailserver"
|
"github.com/status-im/status-go/mailserver"
|
||||||
"github.com/status-im/status-go/params"
|
"github.com/status-im/status-go/params"
|
||||||
"github.com/status-im/status-go/services/ext"
|
"github.com/status-im/status-go/services/ext"
|
||||||
"github.com/status-im/status-go/services/incentivisation"
|
|
||||||
"github.com/status-im/status-go/services/nodebridge"
|
"github.com/status-im/status-go/services/nodebridge"
|
||||||
"github.com/status-im/status-go/services/peer"
|
"github.com/status-im/status-go/services/peer"
|
||||||
"github.com/status-im/status-go/services/personal"
|
"github.com/status-im/status-go/services/personal"
|
||||||
|
@ -57,7 +53,6 @@ var (
|
||||||
ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service")
|
ErrPersonalServiceRegistrationFailure = errors.New("failed to register the personal api service")
|
||||||
ErrStatusServiceRegistrationFailure = errors.New("failed to register the Status service")
|
ErrStatusServiceRegistrationFailure = errors.New("failed to register the Status service")
|
||||||
ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service")
|
ErrPeerServiceRegistrationFailure = errors.New("failed to register the Peer service")
|
||||||
ErrIncentivisationServiceRegistrationFailure = errors.New("failed to register the Incentivisation service")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// All general log messages in this package should be routed through this logger.
|
// All general log messages in this package should be routed through this logger.
|
||||||
|
@ -154,11 +149,6 @@ func activateNodeServices(stack *node.Node, config *params.NodeConfig, db *level
|
||||||
return fmt.Errorf("%v: %v", ErrWakuServiceRegistrationFailure, err)
|
return fmt.Errorf("%v: %v", ErrWakuServiceRegistrationFailure, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// start incentivisation service
|
|
||||||
if err := activateIncentivisationService(stack, config); err != nil {
|
|
||||||
return fmt.Errorf("%v: %v", ErrIncentivisationServiceRegistrationFailure, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// start status service.
|
// start status service.
|
||||||
if err := activateStatusService(stack, config); err != nil {
|
if err := activateStatusService(stack, config); err != nil {
|
||||||
return fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err)
|
return fmt.Errorf("%v: %v", ErrStatusServiceRegistrationFailure, err)
|
||||||
|
@ -497,49 +487,6 @@ func createWakuService(ctx *node.ServiceContext, wakuCfg *params.WakuConfig, clu
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// activateIncentivisationService configures Whisper and adds it to the given node.
|
|
||||||
func activateIncentivisationService(stack *node.Node, config *params.NodeConfig) (err error) {
|
|
||||||
if !config.WhisperConfig.Enabled {
|
|
||||||
logger.Info("SHH protocol is disabled")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.IncentivisationConfig.Enabled {
|
|
||||||
logger.Info("Incentivisation is disabled")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("activating incentivisation")
|
|
||||||
// TODO(dshulyak) add a config option to enable it by default, but disable if app is started from statusd
|
|
||||||
return stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
var w *nodebridge.WhisperService
|
|
||||||
if err := ctx.Service(&w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
incentivisationConfig := &incentivisation.ServiceConfig{
|
|
||||||
ContractAddress: config.IncentivisationConfig.ContractAddress,
|
|
||||||
RPCEndpoint: config.IncentivisationConfig.RPCEndpoint,
|
|
||||||
IP: config.IncentivisationConfig.IP,
|
|
||||||
Port: config.IncentivisationConfig.Port,
|
|
||||||
}
|
|
||||||
privateKey, err := crypto.HexToECDSA(config.NodeKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client, err := ethclient.DialContext(context.TODO(), incentivisationConfig.RPCEndpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
contract, err := incentivisation.NewContract(gethcommon.HexToAddress(incentivisationConfig.ContractAddress), client, client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return incentivisation.New(privateKey, w.Whisper.PublicWhisperAPI(), incentivisationConfig, contract), nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseNodes creates list of enode.Node out of enode strings.
|
// parseNodes creates list of enode.Node out of enode strings.
|
||||||
func parseNodes(enodes []string) []*enode.Node {
|
func parseNodes(enodes []string) []*enode.Node {
|
||||||
var nodes []*enode.Node
|
var nodes []*enode.Node
|
||||||
|
|
|
@ -193,34 +193,6 @@ type WakuConfig struct {
|
||||||
BloomFilterMode bool
|
BloomFilterMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// IncentivisationConfig holds incentivisation-related configuration
|
|
||||||
type IncentivisationConfig struct {
|
|
||||||
// Enabled flag specifies whether protocol is enabled
|
|
||||||
Enabled bool `validate:"required"`
|
|
||||||
// Endpoint for the RPC calls
|
|
||||||
RPCEndpoint string `validate:"required"`
|
|
||||||
// Contract address
|
|
||||||
ContractAddress string `validate:"required"`
|
|
||||||
// IP address that is used
|
|
||||||
IP string `validate:"required"`
|
|
||||||
// Port
|
|
||||||
Port uint16 `validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// String dumps config object as nicely indented JSON
|
|
||||||
func (c *IncentivisationConfig) String() string {
|
|
||||||
data, _ := json.MarshalIndent(c, "", " ") // nolint: gas
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates the IncentivisationConfig struct and returns an error if inconsistent values are found
|
|
||||||
func (c *IncentivisationConfig) Validate(validate *validator.Validate) error {
|
|
||||||
if err := validate.Struct(c); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
// SwarmConfig
|
// SwarmConfig
|
||||||
// ----------
|
// ----------
|
||||||
|
@ -435,9 +407,6 @@ type NodeConfig struct {
|
||||||
// BridgeConfig provides a configuration for Whisper-Waku bridge.
|
// BridgeConfig provides a configuration for Whisper-Waku bridge.
|
||||||
BridgeConfig BridgeConfig `json:"BridgeConfig" validate:"structonly"`
|
BridgeConfig BridgeConfig `json:"BridgeConfig" validate:"structonly"`
|
||||||
|
|
||||||
// IncentivisationConfig extra configuration for incentivisation service
|
|
||||||
IncentivisationConfig IncentivisationConfig `json:"IncentivisationConfig," validate:"structonly"`
|
|
||||||
|
|
||||||
// ShhextConfig extra configuration for service running under shhext namespace.
|
// ShhextConfig extra configuration for service running under shhext namespace.
|
||||||
ShhextConfig ShhextConfig `json:"ShhextConfig," validate:"structonly"`
|
ShhextConfig ShhextConfig `json:"ShhextConfig," validate:"structonly"`
|
||||||
|
|
||||||
|
@ -862,11 +831,6 @@ func (c *NodeConfig) validateChildStructs(validate *validator.Validate) error {
|
||||||
if err := c.ShhextConfig.Validate(validate); err != nil {
|
if err := c.ShhextConfig.Validate(validate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.IncentivisationConfig.Enabled {
|
|
||||||
if err := c.IncentivisationConfig.Validate(validate); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package incentivisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PublicAPI represents a set of APIs from the `web3.peer` namespace.
|
|
||||||
type PublicAPI struct {
|
|
||||||
s *Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAPI creates an instance of the peer API.
|
|
||||||
func NewAPI(s *Service) *PublicAPI {
|
|
||||||
return &PublicAPI{s: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *PublicAPI) Registered(context context.Context) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package incentivisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/mailserver/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Contract interface {
|
|
||||||
Vote(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error)
|
|
||||||
GetCurrentSession(opts *bind.CallOpts) (*big.Int, error)
|
|
||||||
Registered(opts *bind.CallOpts, publicKey []byte) (bool, error)
|
|
||||||
RegisterNode(opts *bind.TransactOpts, publicKey []byte, ip uint32, port uint16) (*types.Transaction, error)
|
|
||||||
ActiveNodeCount(opts *bind.CallOpts) (*big.Int, error)
|
|
||||||
InactiveNodeCount(opts *bind.CallOpts) (*big.Int, error)
|
|
||||||
GetNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error)
|
|
||||||
GetInactiveNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error)
|
|
||||||
VoteSync(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContractImpl struct {
|
|
||||||
registry.NodesV2
|
|
||||||
client *ethclient.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// VoteSync votes on the contract and wait until the transaction has been accepted, returns an error otherwise
|
|
||||||
func (c *ContractImpl) VoteSync(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*types.Transaction, error) {
|
|
||||||
tx, err := c.Vote(opts, joinNodes, removeNodes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
receipt, _ := c.client.TransactionReceipt(context.TODO(), tx.Hash())
|
|
||||||
if receipt != nil {
|
|
||||||
if receipt.Status == 0 {
|
|
||||||
return nil, errors.New("Invalid receipt")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContract creates a new instance of Contract, bound to a specific deployed contract.
|
|
||||||
func NewContract(address gethcommon.Address, backend bind.ContractBackend, client *ethclient.Client) (Contract, error) {
|
|
||||||
contract := &ContractImpl{}
|
|
||||||
contract.client = client
|
|
||||||
|
|
||||||
caller, err := registry.NewNodesV2Caller(address, backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
contract.NodesV2Caller = *caller
|
|
||||||
|
|
||||||
transactor, err := registry.NewNodesV2Transactor(address, backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
contract.NodesV2Transactor = *transactor
|
|
||||||
|
|
||||||
filterer, err := registry.NewNodesV2Filterer(address, backend)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
contract.NodesV2Filterer = *filterer
|
|
||||||
|
|
||||||
return contract, nil
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
package incentivisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/russolsen/transit"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StatusMessageContent struct {
|
|
||||||
ChatID string
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatusMessage struct {
|
|
||||||
Text string
|
|
||||||
ContentT string
|
|
||||||
MessageT string
|
|
||||||
Clock int64
|
|
||||||
Timestamp int64
|
|
||||||
Content StatusMessageContent
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTextStatusMessage creates a StatusMessage.
|
|
||||||
func CreateTextStatusMessage(text string, chatID string) StatusMessage {
|
|
||||||
ts := time.Now().Unix() * 1000
|
|
||||||
|
|
||||||
return StatusMessage{
|
|
||||||
Text: text,
|
|
||||||
ContentT: "text/plain",
|
|
||||||
MessageT: "public-group-user-message",
|
|
||||||
Clock: ts * 100,
|
|
||||||
Timestamp: ts,
|
|
||||||
Content: StatusMessageContent{ChatID: chatID, Text: text},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func EncodeMessage(content string, chatID string) ([]byte, error) {
|
|
||||||
value := CreateTextStatusMessage(content, chatID)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
encoder := NewMessageEncoder(&buf)
|
|
||||||
if err := encoder.Encode(value); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMessageEncoder returns a new Transit encoder
|
|
||||||
// that can encode StatusMessage values.
|
|
||||||
// More about Transit: https://github.com/cognitect/transit-format
|
|
||||||
func NewMessageEncoder(w io.Writer) *transit.Encoder {
|
|
||||||
encoder := transit.NewEncoder(w, false)
|
|
||||||
encoder.AddHandler(statusMessageType, defaultStatusMessageValueEncoder)
|
|
||||||
return encoder
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
statusMessageType = reflect.TypeOf(StatusMessage{})
|
|
||||||
defaultStatusMessageValueEncoder = &statusMessageValueEncoder{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type statusMessageValueEncoder struct{}
|
|
||||||
|
|
||||||
func (statusMessageValueEncoder) IsStringable(reflect.Value) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (statusMessageValueEncoder) Encode(e transit.Encoder, value reflect.Value, asString bool) error {
|
|
||||||
message := value.Interface().(StatusMessage)
|
|
||||||
taggedValue := transit.TaggedValue{
|
|
||||||
Tag: "c4",
|
|
||||||
Value: []interface{}{
|
|
||||||
message.Text,
|
|
||||||
message.ContentT,
|
|
||||||
transit.Keyword(message.MessageT),
|
|
||||||
message.Clock,
|
|
||||||
message.Timestamp,
|
|
||||||
map[interface{}]interface{}{
|
|
||||||
transit.Keyword("chat-id"): message.Content.ChatID,
|
|
||||||
transit.Keyword("text"): message.Content.Text,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return e.EncodeInterface(taggedValue, false)
|
|
||||||
}
|
|
|
@ -1,494 +0,0 @@
|
||||||
package incentivisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
"github.com/status-im/status-go/protocol/transport"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
gasLimit = 1001000
|
|
||||||
pingIntervalAllowance = 240
|
|
||||||
tickerInterval = 30
|
|
||||||
defaultTopic = "status-incentivisation-topic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Enode struct {
|
|
||||||
PublicKey []byte
|
|
||||||
IP net.IP
|
|
||||||
Port uint16
|
|
||||||
JoiningSession uint32
|
|
||||||
ActiveSession uint32
|
|
||||||
Active bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatEnodeURL(publicKey string, ip string, port uint16) string {
|
|
||||||
return fmt.Sprintf("enode://%s:%s:%d", publicKey, ip, port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Enode) toEnodeURL() string {
|
|
||||||
return formatEnodeURL(n.PublicKeyString(), n.IP.String(), n.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Enode) PublicKeyString() string {
|
|
||||||
return hex.EncodeToString(n.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceConfig struct {
|
|
||||||
RPCEndpoint string
|
|
||||||
ContractAddress string
|
|
||||||
IP string
|
|
||||||
Port uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
w types.PublicWhisperAPI
|
|
||||||
whisperKeyID string
|
|
||||||
whisperSymKeyID string
|
|
||||||
whisperFilterID string
|
|
||||||
nodes map[string]*Enode
|
|
||||||
ticker *time.Ticker
|
|
||||||
quit chan struct{}
|
|
||||||
config *ServiceConfig
|
|
||||||
contract Contract
|
|
||||||
privateKey *ecdsa.PrivateKey
|
|
||||||
log log.Logger
|
|
||||||
// The first round we will not be voting, as we might have incomplete data
|
|
||||||
initialSession uint64
|
|
||||||
// The current session
|
|
||||||
currentSession uint64
|
|
||||||
whisperPings map[string][]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new incentivization Service
|
|
||||||
func New(prv *ecdsa.PrivateKey, w types.PublicWhisperAPI, config *ServiceConfig, contract Contract) *Service {
|
|
||||||
logger := log.New("package", "status-go/incentivisation/service")
|
|
||||||
return &Service{
|
|
||||||
w: w,
|
|
||||||
config: config,
|
|
||||||
privateKey: prv,
|
|
||||||
log: logger,
|
|
||||||
contract: contract,
|
|
||||||
nodes: make(map[string]*Enode),
|
|
||||||
whisperPings: make(map[string][]uint32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protocols returns a new protocols list. In this case, there are none.
|
|
||||||
func (s *Service) Protocols() []p2p.Protocol {
|
|
||||||
return []p2p.Protocol{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIs returns a list of new APIs.
|
|
||||||
func (s *Service) APIs() []rpc.API {
|
|
||||||
apis := []rpc.API{
|
|
||||||
{
|
|
||||||
Namespace: "incentivisation",
|
|
||||||
Version: "1.0",
|
|
||||||
Service: NewAPI(s),
|
|
||||||
Public: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return apis
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkRegistered checks that a node is registered with the contract
|
|
||||||
func (s *Service) checkRegistered() error {
|
|
||||||
registered, err := s.registered()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("error querying contract", "registered", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if registered {
|
|
||||||
s.log.Debug("Already registered")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err = s.register()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("error querying contract", "registered", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureSession checks if we are in a new session and updates the session if so
|
|
||||||
func (s *Service) ensureSession() (bool, error) {
|
|
||||||
session, err := s.GetCurrentSession()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("failed to get current session", "err", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if session != s.currentSession {
|
|
||||||
s.currentSession = session
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPings checks we have received the expected pings since it was last called
|
|
||||||
func (s *Service) checkPings() map[string]bool {
|
|
||||||
result := make(map[string]bool)
|
|
||||||
now := time.Now().Unix()
|
|
||||||
s.log.Debug("checking votes", "votes", s.whisperPings)
|
|
||||||
for enodeID, timestamps := range s.whisperPings {
|
|
||||||
result[enodeID] = true
|
|
||||||
|
|
||||||
if len(timestamps) < 2 {
|
|
||||||
s.log.Debug("Node failed check", "enodeID", enodeID)
|
|
||||||
result[enodeID] = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(timestamps, func(i, j int) bool { return timestamps[i] < timestamps[j] })
|
|
||||||
timestamps = append(timestamps, uint32(now))
|
|
||||||
for i := 1; i < len(timestamps); i++ {
|
|
||||||
|
|
||||||
if timestamps[i]-timestamps[i-1] > pingIntervalAllowance {
|
|
||||||
result[enodeID] = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if result[enodeID] {
|
|
||||||
s.log.Debug("Node passed check", "enodeID", enodeID)
|
|
||||||
} else {
|
|
||||||
s.log.Debug("Node failed check", "enodeID", enodeID)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
s.log.Debug("voting result", "result", result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform is the main loop, it posts a ping, registers with the contract, check the pings and votes
|
|
||||||
func (s *Service) perform() error {
|
|
||||||
hash, err := s.postPing()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("Could not post ping", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.log.Debug("Posted ping", "hash", hash)
|
|
||||||
|
|
||||||
err = s.FetchEnodes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.fetchMessages()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.checkRegistered()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("Could not check if node is registered with the contract", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// This actually updates the session
|
|
||||||
newSession, err := s.ensureSession()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("Could not check session", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !newSession {
|
|
||||||
s.log.Debug("Not a new session idling")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := s.checkPings()
|
|
||||||
err = s.vote(result)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("Could not vote", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset whisper pings
|
|
||||||
s.whisperPings = make(map[string][]uint32)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// vote reports to the contract the decisions of the votes
|
|
||||||
func (s *Service) vote(result map[string]bool) error {
|
|
||||||
var behavingNodes []gethcommon.Address
|
|
||||||
var misbehavingNodes []gethcommon.Address
|
|
||||||
auth := s.auth()
|
|
||||||
|
|
||||||
for enodeIDString, passedCheck := range result {
|
|
||||||
enodeID, err := hex.DecodeString(enodeIDString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if passedCheck {
|
|
||||||
behavingNodes = append(behavingNodes, publicKeyBytesToAddress(enodeID))
|
|
||||||
} else {
|
|
||||||
misbehavingNodes = append(misbehavingNodes, publicKeyBytesToAddress(enodeID))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.contract.VoteSync(&bind.TransactOpts{
|
|
||||||
GasLimit: gasLimit,
|
|
||||||
From: auth.From,
|
|
||||||
Signer: auth.Signer,
|
|
||||||
}, behavingNodes, misbehavingNodes)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) startTicker() {
|
|
||||||
s.ticker = time.NewTicker(tickerInterval * time.Second)
|
|
||||||
s.quit = make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.ticker.C:
|
|
||||||
err := s.perform()
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("could not execute tick", "err", err)
|
|
||||||
}
|
|
||||||
case <-s.quit:
|
|
||||||
s.ticker.Stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Start(server *p2p.Server) error {
|
|
||||||
s.log.Info("Incentivisation service started", "address", s.addressString(), "publickey", s.publicKeyString())
|
|
||||||
s.startTicker()
|
|
||||||
|
|
||||||
session, err := s.GetCurrentSession()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.initialSession = session
|
|
||||||
s.currentSession = session
|
|
||||||
|
|
||||||
whisperKeyID, err := s.w.AddPrivateKey(context.TODO(), crypto.FromECDSA(s.privateKey))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.whisperKeyID = whisperKeyID
|
|
||||||
|
|
||||||
whisperSymKeyID, err := s.w.GenerateSymKeyFromPassword(context.TODO(), defaultTopic)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.whisperSymKeyID = whisperSymKeyID
|
|
||||||
|
|
||||||
criteria := types.Criteria{
|
|
||||||
SymKeyID: whisperSymKeyID,
|
|
||||||
Topics: []types.TopicType{toWhisperTopic(defaultTopic)},
|
|
||||||
}
|
|
||||||
filterID, err := s.w.NewMessageFilter(criteria)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("could not create filter", "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.whisperFilterID = filterID
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop is run when a service is stopped.
|
|
||||||
func (s *Service) Stop() error {
|
|
||||||
s.log.Info("Incentivisation service stopped")
|
|
||||||
_, err := s.w.DeleteKeyPair(context.TODO(), s.whisperKeyID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) publicKeyBytes() []byte {
|
|
||||||
return crypto.FromECDSAPub(&s.privateKey.PublicKey)[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) GetCurrentSession() (uint64, error) {
|
|
||||||
response, err := s.contract.GetCurrentSession(nil)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("failed to get current session", "err", err)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return response.Uint64(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) registered() (bool, error) {
|
|
||||||
response, err := s.contract.Registered(nil, s.publicKeyBytes())
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) register() (bool, error) {
|
|
||||||
auth := s.auth()
|
|
||||||
ip, err := ip2Long(s.config.IP)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.contract.RegisterNode(&bind.TransactOpts{
|
|
||||||
GasLimit: gasLimit,
|
|
||||||
From: auth.From,
|
|
||||||
Signer: auth.Signer,
|
|
||||||
}, s.publicKeyBytes(), ip, s.config.Port)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) FetchEnodes() error {
|
|
||||||
one := big.NewInt(1)
|
|
||||||
|
|
||||||
activeNodeCount, err := s.contract.ActiveNodeCount(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.log.Debug("fetched active node count", "count", activeNodeCount)
|
|
||||||
for i := big.NewInt(0); i.Cmp(activeNodeCount) < 0; i.Add(i, one) {
|
|
||||||
publicKey, ip, port, joiningSession, activeSession, err := s.contract.GetNode(nil, i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &Enode{
|
|
||||||
PublicKey: publicKey,
|
|
||||||
IP: int2ip(ip),
|
|
||||||
Port: port,
|
|
||||||
JoiningSession: joiningSession,
|
|
||||||
ActiveSession: activeSession,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Debug("adding node", "node", node.toEnodeURL())
|
|
||||||
if node.PublicKeyString() != s.publicKeyString() {
|
|
||||||
s.nodes[node.PublicKeyString()] = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inactiveNodeCount, err := s.contract.InactiveNodeCount(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.log.Debug("fetched inactive node count", "count", inactiveNodeCount)
|
|
||||||
for i := big.NewInt(0); i.Cmp(inactiveNodeCount) < 0; i.Add(i, one) {
|
|
||||||
publicKey, ip, port, joiningSession, activeSession, err := s.contract.GetInactiveNode(nil, i)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
node := &Enode{
|
|
||||||
PublicKey: publicKey,
|
|
||||||
IP: int2ip(ip),
|
|
||||||
Port: port,
|
|
||||||
JoiningSession: joiningSession,
|
|
||||||
ActiveSession: activeSession,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.log.Debug("adding node", "node", node.toEnodeURL())
|
|
||||||
if node.PublicKeyString() != s.publicKeyString() {
|
|
||||||
s.nodes[node.PublicKeyString()] = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) publicKeyString() string {
|
|
||||||
return hex.EncodeToString(s.publicKeyBytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) addressString() string {
|
|
||||||
buf := crypto.Keccak256Hash(s.publicKeyBytes())
|
|
||||||
address := buf[12:]
|
|
||||||
|
|
||||||
return hex.EncodeToString(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
// postPing publishes a whisper message
|
|
||||||
func (s *Service) postPing() (types.HexBytes, error) {
|
|
||||||
msg := transport.DefaultMessage()
|
|
||||||
|
|
||||||
msg.Topic = toWhisperTopic(defaultTopic)
|
|
||||||
|
|
||||||
enodeURL := formatEnodeURL(s.publicKeyString(), s.config.IP, s.config.Port)
|
|
||||||
payload, err := EncodeMessage(enodeURL, defaultTopic)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.Payload = payload
|
|
||||||
msg.SigID = s.whisperKeyID
|
|
||||||
msg.SymKeyID = s.whisperSymKeyID
|
|
||||||
|
|
||||||
return s.w.Post(context.TODO(), msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetchMessages checks for whisper messages
|
|
||||||
func (s *Service) fetchMessages() error {
|
|
||||||
messages, err := s.w.GetFilterMessages(s.whisperFilterID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(messages); i++ {
|
|
||||||
signature := hex.EncodeToString(messages[i].Sig[1:])
|
|
||||||
timestamp := messages[i].Timestamp
|
|
||||||
if s.nodes[signature] != nil {
|
|
||||||
s.whisperPings[signature] = append(s.whisperPings[signature], timestamp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) auth() *bind.TransactOpts {
|
|
||||||
return bind.NewKeyedTransactor(s.privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ip2Long(ip string) (uint32, error) {
|
|
||||||
var long uint32
|
|
||||||
err := binary.Read(bytes.NewBuffer(net.ParseIP(ip).To4()), binary.BigEndian, &long)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return long, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toWhisperTopic(s string) types.TopicType {
|
|
||||||
return types.BytesToTopic(crypto.Keccak256([]byte(s)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func int2ip(nn uint32) net.IP {
|
|
||||||
ip := make(net.IP, 4)
|
|
||||||
binary.BigEndian.PutUint32(ip, nn)
|
|
||||||
return ip
|
|
||||||
}
|
|
||||||
|
|
||||||
func publicKeyBytesToAddress(publicKey []byte) gethcommon.Address {
|
|
||||||
buf := crypto.Keccak256Hash(publicKey)
|
|
||||||
address := buf[12:]
|
|
||||||
|
|
||||||
return gethcommon.HexToAddress(hex.EncodeToString(address))
|
|
||||||
}
|
|
|
@ -1,206 +0,0 @@
|
||||||
package incentivisation
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"math/big"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
|
||||||
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
||||||
gethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
nodeOne = []byte{0x01}
|
|
||||||
nodeTwo = []byte{0x02}
|
|
||||||
)
|
|
||||||
|
|
||||||
type MockContract struct {
|
|
||||||
currentSession *big.Int
|
|
||||||
activeNodes [][]byte
|
|
||||||
inactiveNodes [][]byte
|
|
||||||
votes []Vote
|
|
||||||
}
|
|
||||||
|
|
||||||
type Vote struct {
|
|
||||||
joinNodes []gethcommon.Address
|
|
||||||
removeNodes []gethcommon.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockWhisper struct {
|
|
||||||
sentMessages []types.NewMessage
|
|
||||||
filterMessages []*types.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildMockContract() *MockContract {
|
|
||||||
contract := &MockContract{
|
|
||||||
currentSession: big.NewInt(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
contract.activeNodes = append(contract.activeNodes, nodeOne)
|
|
||||||
contract.inactiveNodes = append(contract.activeNodes, nodeTwo)
|
|
||||||
return contract
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockContract) Vote(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*gethtypes.Transaction, error) {
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockContract) VoteSync(opts *bind.TransactOpts, joinNodes []gethcommon.Address, removeNodes []gethcommon.Address) (*gethtypes.Transaction, error) {
|
|
||||||
c.votes = append(c.votes, Vote{
|
|
||||||
joinNodes: joinNodes,
|
|
||||||
removeNodes: removeNodes,
|
|
||||||
})
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockContract) GetCurrentSession(opts *bind.CallOpts) (*big.Int, error) {
|
|
||||||
return c.currentSession, nil
|
|
||||||
}
|
|
||||||
func (c *MockContract) Registered(opts *bind.CallOpts, publicKey []byte) (bool, error) {
|
|
||||||
|
|
||||||
for _, e := range c.activeNodes {
|
|
||||||
if bytes.Equal(publicKey, e) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, e := range c.inactiveNodes {
|
|
||||||
if bytes.Equal(publicKey, e) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockContract) RegisterNode(opts *bind.TransactOpts, publicKey []byte, ip uint32, port uint16) (*gethtypes.Transaction, error) {
|
|
||||||
c.inactiveNodes = append(c.inactiveNodes, publicKey)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (c *MockContract) ActiveNodeCount(opts *bind.CallOpts) (*big.Int, error) {
|
|
||||||
return big.NewInt(int64(len(c.activeNodes))), nil
|
|
||||||
}
|
|
||||||
func (c *MockContract) InactiveNodeCount(opts *bind.CallOpts) (*big.Int, error) {
|
|
||||||
return big.NewInt(int64(len(c.inactiveNodes))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockContract) GetNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error) {
|
|
||||||
return c.activeNodes[index.Int64()], 0, 0, 0, 0, nil
|
|
||||||
}
|
|
||||||
func (c *MockContract) GetInactiveNode(opts *bind.CallOpts, index *big.Int) ([]byte, uint32, uint16, uint32, uint32, error) {
|
|
||||||
return c.inactiveNodes[index.Int64()], 0, 0, 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *MockWhisper) Post(ctx context.Context, req types.NewMessage) ([]byte, error) {
|
|
||||||
w.sentMessages = append(w.sentMessages, req)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
func (w *MockWhisper) NewMessageFilter(req types.Criteria) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
func (w *MockWhisper) AddPrivateKey(ctx context.Context, privateKey types.HexBytes) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
func (w *MockWhisper) DeleteKeyPair(ctx context.Context, key string) (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
func (w *MockWhisper) GenerateSymKeyFromPassword(ctx context.Context, passwd string) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
func (w *MockWhisper) GetFilterMessages(id string) ([]*types.Message, error) {
|
|
||||||
return w.filterMessages, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIncentivisationSuite(t *testing.T) {
|
|
||||||
suite.Run(t, new(IncentivisationSuite))
|
|
||||||
}
|
|
||||||
|
|
||||||
type IncentivisationSuite struct {
|
|
||||||
suite.Suite
|
|
||||||
service *Service
|
|
||||||
mockWhisper *MockWhisper
|
|
||||||
mockContract *MockContract
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IncentivisationSuite) SetupTest() {
|
|
||||||
privateKey, err := crypto.GenerateKey()
|
|
||||||
config := &ServiceConfig{
|
|
||||||
IP: "192.168.1.1",
|
|
||||||
}
|
|
||||||
contract := BuildMockContract()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
w := &MockWhisper{}
|
|
||||||
s.service = New(privateKey, w, config, contract)
|
|
||||||
s.mockWhisper = w
|
|
||||||
s.mockContract = contract
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IncentivisationSuite) TestStart() {
|
|
||||||
err := s.service.Start(nil)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *IncentivisationSuite) TestPerform() {
|
|
||||||
err := s.service.Start(nil)
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
err = s.service.perform()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// It registers with the contract if not registered
|
|
||||||
registered, err := s.service.registered()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
s.Require().Equal(true, registered)
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
// Add some envelopes
|
|
||||||
s.mockWhisper.filterMessages = []*types.Message{
|
|
||||||
{
|
|
||||||
// We strip the first byte when processing
|
|
||||||
Sig: append(nodeOne, nodeOne[0]),
|
|
||||||
Timestamp: uint32(now - pingIntervalAllowance),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Sig: append(nodeOne, nodeOne[0]),
|
|
||||||
Timestamp: uint32(now - (pingIntervalAllowance * 2)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Sig: append(nodeTwo, nodeTwo[0]),
|
|
||||||
Timestamp: uint32(now - (pingIntervalAllowance * 2)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// It publishes a ping on whisper
|
|
||||||
s.Require().Equal(1, len(s.mockWhisper.sentMessages))
|
|
||||||
|
|
||||||
// It should not vote
|
|
||||||
s.Require().Equal(0, len(s.mockContract.votes))
|
|
||||||
|
|
||||||
// We increase the session
|
|
||||||
s.mockContract.currentSession = s.mockContract.currentSession.Add(s.mockContract.currentSession, big.NewInt(1))
|
|
||||||
|
|
||||||
// We perform again
|
|
||||||
err = s.service.perform()
|
|
||||||
s.Require().NoError(err)
|
|
||||||
|
|
||||||
// it should now vote
|
|
||||||
s.Require().Equal(1, len(s.mockContract.votes))
|
|
||||||
// Node one should have been voted up
|
|
||||||
s.Require().Equal(1, len(s.mockContract.votes[0].joinNodes))
|
|
||||||
s.Require().Equal(publicKeyBytesToAddress(nodeOne), s.mockContract.votes[0].joinNodes[0])
|
|
||||||
// Node two should have been voted down
|
|
||||||
s.Require().Equal(1, len(s.mockContract.votes[0].removeNodes))
|
|
||||||
s.Require().Equal(publicKeyBytesToAddress(nodeTwo), s.mockContract.votes[0].removeNodes[0])
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue