add: staking store db integration, task mining execution

This commit is contained in:
bahadylbekov 2020-10-31 06:48:15 +03:00
parent 9219e483a1
commit 239e54b0dd
11 changed files with 282 additions and 54 deletions

View File

@ -1,6 +1,8 @@
package config
import (
"fmt"
"github.com/spf13/viper"
)
@ -13,6 +15,7 @@ type Config struct {
Ethereum EthereumConfig `mapstructure:"ethereum"`
Filecoin FilecoinConfig `mapstructure:"filecoin"`
PubSub PubSubConfig `mapstructure:"pubSub"`
Store StoreConfig `mapstructure:"store"`
}
type EthereumConfig struct {
@ -31,8 +34,17 @@ type PubSubConfig struct {
ProtocolID string `mapstructure:"protocolID"`
}
type StoreConfig struct {
DatabaseURL string `mapstructure:"database_url"`
}
// NewConfig creates a new config based on default values or provided .env file
func NewConfig(configPath string) (*Config, error) {
dbName := "dione"
username := "user"
password := "password"
dbURL := fmt.Sprintf("host=localhost user=%s password=%s dbname=%s sslmode=disable", username, password, dbName)
cfg := &Config{
ListenAddr: "localhost",
ListenPort: ":8000",
@ -45,6 +57,9 @@ func NewConfig(configPath string) (*Config, error) {
PubSub: PubSubConfig{
ProtocolID: "p2p-oracle",
},
Store: StoreConfig{
DatabaseURL: dbURL,
},
}
viper.SetConfigFile(configPath)

View File

@ -13,18 +13,6 @@ import (
"golang.org/x/xerrors"
)
type MinerWallet interface {
WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error)
}
type MinerBase struct {
MinerStake types.BigInt
NetworkStake types.BigInt
WorkerKey address.Address
PrevBeaconEntry types.BeaconEntry
BeaconEntries []types.BeaconEntry
}
type SignFunc func(context.Context, address.Address, []byte) (*crypto.Signature, error)
func ComputeVRF(ctx context.Context, sign SignFunc, worker address.Address, sigInput []byte) ([]byte, error) {
@ -57,7 +45,7 @@ func VerifyVRF(ctx context.Context, worker address.Address, vrfBase, vrfproof []
}
func IsRoundWinner(ctx context.Context, round types.TaskEpoch,
worker address.Address, brand types.BeaconEntry, mbi *MinerBase, a MinerWallet) (*types.ElectionProof, error) {
worker address.Address, brand types.BeaconEntry, mb *MinerBase, a MinerAPI) (*types.ElectionProof, error) {
buf := new(bytes.Buffer)
if err := worker.MarshalCBOR(buf); err != nil {
@ -69,13 +57,13 @@ func IsRoundWinner(ctx context.Context, round types.TaskEpoch,
return nil, xerrors.Errorf("failed to draw randomness: %w", err)
}
vrfout, err := ComputeVRF(ctx, a.WalletSign, mbi.WorkerKey, electionRand)
vrfout, err := ComputeVRF(ctx, a.WalletSign, mb.WorkerKey, electionRand)
if err != nil {
return nil, xerrors.Errorf("failed to compute VRF: %w", err)
}
ep := &types.ElectionProof{VRFProof: vrfout}
j := ep.ComputeWinCount(mbi.MinerStake, mbi.NetworkStake)
j := ep.ComputeWinCount(mb.MinerStake, mb.NetworkStake)
ep.WinCount = j
if j < 1 {
return nil, nil

110
consensus/miner.go Normal file
View File

@ -0,0 +1,110 @@
package consensus
import (
"bytes"
"context"
"github.com/Secured-Finance/dione/types"
"github.com/ethereum/go-ethereum/common"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/sirupsen/logrus"
"golang.org/x/xerrors"
)
type Miner struct {
address address.Address
api MinerAPI
}
type MinerAPI interface {
WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error)
// TODO: get miner base based on epoch;
}
type MinerBase struct {
MinerStake types.BigInt
NetworkStake types.BigInt
WorkerKey address.Address
EthWallet common.Address
PrevBeaconEntry types.BeaconEntry
BeaconEntries []types.BeaconEntry
NullRounds types.TaskEpoch
}
type MiningBase struct {
epoch types.TaskEpoch
nullRounds types.TaskEpoch
}
func NewMinerBase(minerStake, networkStake types.BigInt, minerWallet address.Address,
minerEthWallet common.Address, prev types.BeaconEntry, entries []types.BeaconEntry) *MinerBase {
return &MinerBase{
MinerStake: minerStake,
NetworkStake: networkStake,
WorkerKey: minerWallet,
EthWallet: minerEthWallet,
PrevBeaconEntry: prev,
BeaconEntries: entries,
}
}
func NewMiningBase() *MiningBase {
return &MiningBase{
nullRounds: 0,
}
}
// Start, Stop mining functions
func (m *Miner) MineTask(ctx context.Context, base *MiningBase, mb *MinerBase) (*types.DioneTask, error) {
round := base.epoch + base.nullRounds + 1
logrus.Debug("attempting to mine the task at epoch: ", round)
prevEntry := mb.PrevBeaconEntry
ticket, err := m.computeTicket(ctx, &prevEntry, base, mb)
if err != nil {
return nil, xerrors.Errorf("scratching ticket failed: %w", err)
}
winner, err := IsRoundWinner(ctx, round, m.address, prevEntry, mb, m.api)
if err != nil {
return nil, xerrors.Errorf("failed to check if we win next round: %w", err)
}
if winner == nil {
return nil, nil
}
return &types.DioneTask{
Miner: m.address,
Ticket: ticket,
ElectionProof: winner,
BeaconEntries: mb.BeaconEntries,
// TODO: signature
Height: round,
}, nil
}
func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry, base *MiningBase, mb *MinerBase) (*types.Ticket, error) {
buf := new(bytes.Buffer)
if err := m.address.MarshalCBOR(buf); err != nil {
return nil, xerrors.Errorf("failed to marshal address to cbor: %w", err)
}
round := base.epoch + base.nullRounds + 1
input, err := DrawRandomness(brand.Data, crypto.DomainSeparationTag_TicketProduction, round-types.TicketRandomnessLookback, buf.Bytes())
if err != nil {
return nil, err
}
vrfOut, err := ComputeVRF(ctx, m.api.WalletSign, mb.WorkerKey, input)
if err != nil {
return nil, err
}
return &types.Ticket{
VRFProof: vrfOut,
}, nil
}

1
go.mod
View File

@ -21,6 +21,7 @@ require (
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/google/uuid v1.1.1
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect
github.com/jmoiron/sqlx v1.2.0
github.com/karalabe/usb v0.0.0-20191104083709-911d15fe12a9 // indirect
github.com/libp2p/go-libp2p v0.11.0
github.com/libp2p/go-libp2p-core v0.6.1

4
go.sum
View File

@ -680,6 +680,8 @@ github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -730,6 +732,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ=
github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU=
@ -1075,6 +1078,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-xmlrpc v0.0.3/go.mod h1:mqc2dz7tP5x5BKlCahN/n+hs7OSZKJkS9JsHNBRlrxA=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=

View File

@ -17,6 +17,7 @@ import (
// TODO: change artifacts for other contracts
type EthereumClient struct {
client *ethclient.Client
ethAddress *common.Address
authTransactor *bind.TransactOpts
oracleEmitter *oracleemitter.OracleEmitterSession
aggregator *aggregator.AggregatorSession
@ -53,6 +54,7 @@ func (c *EthereumClient) Initialize(ctx context.Context, url, privateKey, oracle
if err != nil {
return err
}
c.ethAddress = &c.authTransactor.From
authTransactor := bind.NewKeyedTransactor(ecdsaKey)
c.authTransactor = authTransactor
@ -158,26 +160,6 @@ func (c *EthereumClient) Initialize(ctx context.Context, url, privateKey, oracle
// return TxHash
// }
// func (c *EthereumClient) GenerateAddressFromPrivateKey(private_key string) string {
// privateKey, err := crypto.HexToECDSA(private_key)
// if err != nil {
// c.Logger.Fatal("Failed to generate private key", err)
// }
// publicKey := privateKey.Public()
// publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
// if !ok {
// c.Logger.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
// }
// publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
// c.Logger.Info(hexutil.Encode(publicKeyBytes)[4:])
// address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex()
// return address
// }
func (c *EthereumClient) SubscribeOnOracleEvents(incomingEventsChan chan *oracleemitter.OracleEmitterNewOracleRequest) (event.Subscription, error) {
requestsFilter := c.oracleEmitter.Contract.OracleEmitterFilterer
subscription, err := requestsFilter.WatchNewOracleRequest(&bind.WatchOpts{
@ -224,6 +206,9 @@ func (c *EthereumClient) SubmitRequestAnswer(reqID *big.Int, data string, callba
return nil
}
// Getting total stake in DioneStaking contract, this function could
// be used for storing the total stake and veryfing the stake tokens
// on new tasks
func (c *EthereumClient) GetTotalStake() (*big.Int, error) {
totalStake, err := c.dioneStaking.TotalStake()
if err != nil {
@ -232,6 +217,9 @@ func (c *EthereumClient) GetTotalStake() (*big.Int, error) {
return totalStake, nil
}
// Getting miner stake in DioneStaking contract, this function could
// be used for storing the miner's stake and veryfing the stake tokens
// on new tasks
func (c *EthereumClient) GetMinerStake(minerAddress common.Address) (*big.Int, error) {
minerStake, err := c.dioneStaking.MinerStake(minerAddress)
if err != nil {

35
rpcclient/wallet.go Normal file
View File

@ -0,0 +1,35 @@
package rpcclient
import (
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/sirupsen/logrus"
)
func GenerateEthWalletAddressFromPrivateKey(private_key string) common.Address {
privateKey, err := crypto.HexToECDSA(private_key)
if err != nil {
logrus.Fatal("Failed to generate private key", err)
}
publicKey := privateKey.Public()
publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
logrus.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
}
publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA)
logrus.Info(hexutil.Encode(publicKeyBytes)[4:])
address := crypto.PubkeyToAddress(*publicKeyECDSA)
return address
}
// Convert common.Address type into string for ethereum wallet
func EthWalletToString(ethWallet common.Address) string {
return ethWallet.Hex()
}

View File

@ -2,28 +2,37 @@ package store
import (
"math/big"
"time"
"github.com/Secured-Finance/dione/lib"
"github.com/Secured-Finance/dione/rpcclient"
"github.com/Secured-Finance/dione/types"
"github.com/ethereum/go-ethereum/common"
validation "github.com/go-ozzo/ozzo-validation"
)
// TODO: specify store for staking mechanism
type DioneStakeInfo struct {
MinerStake *big.Int
TotalStake *big.Int
Ethereum *rpcclient.EthereumClient
ID int
MinerStake *big.Int
TotalStake *big.Int
MinerWallet string
MinerEthWallet string
Timestamp time.Time
Ethereum *rpcclient.EthereumClient
}
func NewDioneStakeInfo(minerStake, totalStake *big.Int, ethereumClient *rpcclient.EthereumClient) *DioneStakeInfo {
func NewDioneStakeInfo(minerStake, totalStake *big.Int, minerWallet, minerEthWallet string, ethereumClient *rpcclient.EthereumClient) *DioneStakeInfo {
return &DioneStakeInfo{
MinerStake: minerStake,
TotalStake: totalStake,
Ethereum: ethereumClient,
MinerStake: minerStake,
TotalStake: totalStake,
MinerWallet: minerWallet,
MinerEthWallet: minerEthWallet,
Ethereum: ethereumClient,
}
}
func (d *DioneStakeInfo) UpdateMinerStake(minerAddress common.Address) error {
minerStake, err := d.Ethereum.GetMinerStake(minerAddress)
func (d *DioneStakeInfo) UpdateMinerStake(minerEthAddress common.Address) error {
minerStake, err := d.Ethereum.GetMinerStake(minerEthAddress)
if err != nil {
return err
}
@ -33,7 +42,7 @@ func (d *DioneStakeInfo) UpdateMinerStake(minerAddress common.Address) error {
return nil
}
func (d *DioneStakeInfo) UpdateTotalStake(minerAddress common.Address) error {
func (d *DioneStakeInfo) UpdateTotalStake() error {
totalStake, err := d.Ethereum.GetTotalStake()
if err != nil {
return err
@ -43,3 +52,46 @@ func (d *DioneStakeInfo) UpdateTotalStake(minerAddress common.Address) error {
return nil
}
// Put miner's staking information into the database
func (s *Store) CreateDioneStakeInfo(stakeStore *DioneStakeInfo) error {
if err := stakeStore.Validate(); err != nil {
return err
}
now := lib.Clock.Now()
return s.db.QueryRow(
"INSERT INTO staking (miner_stake, total_stake, miner_wallet, miner_eth_wallet, timestamp) VALUES ($1, $2, $3, $4, $5) RETURNING id",
stakeStore.MinerStake,
stakeStore.TotalStake,
stakeStore.MinerWallet,
stakeStore.MinerEthWallet,
now,
).Scan(&stakeStore.ID)
}
func (s *Store) GetLastStakeInfo(wallet, ethWallet string) (*DioneStakeInfo, error) {
var stake *DioneStakeInfo
if err := s.db.Select(&stake,
`SELECT miner_stake, total_stake, miner_wallet, miner_eth_wallet, timestamp FROM staking ORDER BY TIMESTAMP DESC LIMIT 1 WHERE miner_wallet=$1, miner_eth_wallet=$2`,
wallet,
ethWallet,
); err != nil {
return nil, err
}
return stake, nil
}
// Before puting the data into the database validating all required fields
func (s *DioneStakeInfo) Validate() error {
return validation.ValidateStruct(
s,
validation.Field(&s.MinerStake, validation.Required, validation.By(types.ValidateBigInt(s.MinerStake))),
validation.Field(&s.TotalStake, validation.Required, validation.By(types.ValidateBigInt(s.TotalStake))),
validation.Field(&s.MinerWallet, validation.Required),
validation.Field(&s.MinerEthWallet, validation.Required),
)
}

View File

@ -1,24 +1,45 @@
package store
import (
"database/sql"
"github.com/Secured-Finance/dione/node"
"github.com/jmoiron/sqlx"
)
type Store struct {
db *sql.DB
node *node.Node
genesisTs uint64
db *sqlx.DB
node *node.Node
genesisTs uint64
StakeStorage DioneStakeInfo
// genesisTask *types.DioneTask
}
func NewStore(db *sql.DB, node *node.Node, genesisTs uint64) *Store {
func NewStore(node *node.Node, genesisTs uint64) (*Store, error) {
db, err := newDB(node.Config.Store.DatabaseURL)
if err != nil {
return nil, err
}
defer db.Close()
return &Store{
db: db,
node: node,
genesisTs: genesisTs,
}
}, nil
}
// TODO: connect data base; specify the table for stake storage; queries for stake storage
func newDB(databaseURL string) (*sqlx.DB, error) {
db, err := sqlx.Connect("postgres", databaseURL)
if err != nil {
return nil, err
}
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}
// TODO: Discuss with ChronosX88 about using custom database to decrease I/O bound
// specify the migrations for stake storage;

View File

@ -1,9 +1,11 @@
package types
import (
"errors"
"math/big"
big2 "github.com/filecoin-project/go-state-types/big"
validation "github.com/go-ozzo/ozzo-validation"
)
var EmptyInt = BigInt{}
@ -18,3 +20,13 @@ func BigFromBytes(b []byte) BigInt {
i := big.NewInt(0).SetBytes(b)
return BigInt{Int: i}
}
func ValidateBigInt(i *big.Int) validation.RuleFunc {
return func(value interface{}) error {
bigInt := i.IsInt64()
if !bigInt {
return errors.New("expected big integer")
}
return nil
}
}

View File

@ -11,6 +11,8 @@ import (
// TaskEpoch represents the timestamp of Task computed by the Dione miner
type TaskEpoch int64
const TicketRandomnessLookback = TaskEpoch(1)
func (e TaskEpoch) String() string {
return strconv.FormatInt(int64(e), 10)
}