From 003f49174bc6d612656eff1eeaa951d04db2a2fb Mon Sep 17 00:00:00 2001 From: ChronosX88 Date: Fri, 27 Nov 2020 20:16:08 +0400 Subject: [PATCH] Implement full task validation (signatures/vrfs/wincount/etc.) --- consensus/commit_pool.go | 20 +--- consensus/consensus.go | 183 ++-------------------------------- consensus/leader.go | 24 ++--- consensus/miner.go | 85 ++++++++++++---- consensus/pre_prepare_pool.go | 95 +++++++++++++++++- consensus/prepare_pool.go | 18 +--- consensus/types/message.go | 7 +- consensus/utils.go | 26 +++++ ethclient/stake.go | 4 + go.mod | 3 +- go.sum | 3 +- node/ethereum.go | 11 +- node/node.go | 32 +++--- types/task.go | 30 +++--- 14 files changed, 251 insertions(+), 290 deletions(-) create mode 100644 consensus/utils.go diff --git a/consensus/commit_pool.go b/consensus/commit_pool.go index e910785..ce04874 100644 --- a/consensus/commit_pool.go +++ b/consensus/commit_pool.go @@ -2,8 +2,7 @@ package consensus import ( types2 "github.com/Secured-Finance/dione/consensus/types" - "github.com/Secured-Finance/dione/sigs" - "github.com/Secured-Finance/dione/types" + "github.com/sirupsen/logrus" ) type CommitPool struct { @@ -19,18 +18,8 @@ func NewCommitPool() *CommitPool { func (cp *CommitPool) CreateCommit(prepareMsg *types2.Message, privateKey []byte) (*types2.Message, error) { var message types2.Message message.Type = types2.MessageTypeCommit - var consensusMsg types2.ConsensusMessage - prepareCMessage := prepareMsg.Payload - consensusMsg.ConsensusID = prepareCMessage.ConsensusID - consensusMsg.RequestID = prepareMsg.Payload.RequestID - consensusMsg.CallbackAddress = prepareMsg.Payload.CallbackAddress - consensusMsg.Data = prepareCMessage.Data - signature, err := sigs.Sign(types.SigTypeEd25519, privateKey, []byte(prepareCMessage.Data)) - if err != nil { - return nil, err - } - consensusMsg.Signature = signature.Data - message.Payload = consensusMsg + newCMsg := prepareMsg.Payload + message.Payload = newCMsg return &message, nil } @@ -47,8 +36,9 @@ func (cp *CommitPool) IsExistingCommit(commitMsg *types2.Message) bool { func (cp *CommitPool) IsValidCommit(commit *types2.Message) bool { consensusMsg := commit.Payload - err := sigs.Verify(&types.Signature{Type: types.SigTypeEd25519, Data: consensusMsg.Signature}, commit.From, []byte(consensusMsg.Data)) + err := verifyTaskSignature(consensusMsg) if err != nil { + logrus.Errorf("failed to verify task signature: %v", err) return false } return true diff --git a/consensus/consensus.go b/consensus/consensus.go index 9979c00..0a56eb3 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -4,8 +4,6 @@ import ( "math/big" "sync" - "github.com/Secured-Finance/dione/sigs" - "github.com/Secured-Finance/dione/consensus/types" "github.com/ethereum/go-ethereum/common" @@ -26,22 +24,19 @@ type PBFTConsensusManager struct { commitPool *CommitPool consensusInfo map[string]*ConsensusData ethereumClient *ethclient.EthereumClient + miner *Miner } type ConsensusData struct { - // preparedCount int - // commitCount int mutex sync.Mutex alreadySubmitted bool - // result string - // test bool - // onConsensusFinishCallback func(finalData string) } -func NewPBFTConsensusManager(psb *pubsub.PubSubRouter, minApprovals int, privKey []byte, ethereumClient *ethclient.EthereumClient) *PBFTConsensusManager { +func NewPBFTConsensusManager(psb *pubsub.PubSubRouter, minApprovals int, privKey []byte, ethereumClient *ethclient.EthereumClient, miner *Miner) *PBFTConsensusManager { pcm := &PBFTConsensusManager{} pcm.psb = psb - pcm.prePreparePool = NewPrePreparePool() + pcm.miner = miner + pcm.prePreparePool = NewPrePreparePool(miner) pcm.preparePool = NewPreparePool() pcm.commitPool = NewCommitPool() pcm.minApprovals = minApprovals @@ -54,144 +49,11 @@ func NewPBFTConsensusManager(psb *pubsub.PubSubRouter, minApprovals int, privKey return pcm } -//func (pcm *PBFTConsensusManager) NewTestConsensus(data string, consensusID string, onConsensusFinishCallback func(finalData string)) { -// //consensusID := uuid.New().String() -// cData := &ConsensusData{} -// cData.test = true -// cData.onConsensusFinishCallback = onConsensusFinishCallback -// pcm.Consensuses[consensusID] = cData -// -// // here we will create DioneTask -// -// msg := models.Message{} -// msg.Type = "prepared" -// msg.Payload = make(map[string]interface{}) -// msg.Payload["consensusID"] = consensusID -// msg.Payload["data"] = data -// sign, err := sigs.Sign(types.SigTypeEd25519, pcm.privKey, []byte(data)) -// if err != nil { -// logrus.Warnf("failed to sign data: %w", err) -// return -// } -// msg.Payload["signature"] = string(sign.Data) -// pcm.psb.BroadcastToServiceTopic(&msg) -// -// cData.State = ConsensusPrePrepared -// logrus.Debug("started new consensus: " + consensusID) -//} -// -//func (pcm *PBFTConsensusManager) handlePrePrepareMessage(sender peer.ID, message *models.Message) { -// consensusID := message.Payload["consensusID"].(string) -// if _, ok := pcm.Consensuses[consensusID]; !ok { -// logrus.Warn("Unknown consensus ID,: " + consensusID + ", creating consensusInfo") -// pcm.Consensuses[consensusID] = &ConsensusData{ -// State: ConsensusPrePrepared, -// onConsensusFinishCallback: func(finalData string) {}, -// } -// } -// data := pcm.Consensuses[consensusID] -// logrus.Debug("received pre_prepare msg") -//} -// -//func (pcm *PBFTConsensusManager) handlePreparedMessage(sender peer.ID, message *models.Message) { -// // TODO add check on view of the message -// consensusID := message.Payload["consensusID"].(string) -// if _, ok := pcm.Consensuses[consensusID]; !ok { -// logrus.Warn("Unknown consensus ID,: " + consensusID + ", creating consensusInfo") -// pcm.Consensuses[consensusID] = &ConsensusData{ -// State: ConsensusPrePrepared, -// onConsensusFinishCallback: func(finalData string) {}, -// } -// } -// data := pcm.Consensuses[consensusID] -// logrus.Debug("received prepared msg") -// //data := pcm.Consensuses[consensusID] -// -// // TODO -// // here we can validate miner which produced this task, is he winner, and so on -// // validation steps: -// // 1. validate sender eligibility to mine (check if it has minimal stake) -// // 2. validate sender wincount -// // 3. validate randomness -// // 4. validate vrf -// // 5. validate payload signature -// // 6. validate transaction (get from rpc client and compare with received) -// -// signStr := message.Payload["signature"].(string) -// signRaw := []byte(signStr) -// err := sigs.Verify(&types.Signature{Data: signRaw, Type: types.SigTypeEd25519}, sender, message.Payload["data"].([]byte)) -// if err != nil { -// logrus.Warn("failed to verify data signature") -// return -// } -// -// data.mutex.Lock() -// defer data.mutex.Unlock() -// data.preparedCount++ -// -// if data.preparedCount > 2*pcm.maxFaultNodes+1 { -// msg := models.Message{} -// msg.Payload = make(map[string]interface{}) -// msg.Type = "commit" -// msg.Payload["consensusID"] = consensusID -// msg.Payload["data"] = message.Payload["data"] -// sign, err := sigs.Sign(types.SigTypeEd25519, pcm.privKey, message.Payload["data"].([]byte)) -// if err != nil { -// logrus.Warnf("failed to sign data: %w", err) -// return -// } -// msg.Payload["signature"] = string(sign.Data) -// err = pcm.psb.BroadcastToServiceTopic(&msg) -// if err != nil { -// logrus.Warn("Unable to send COMMIT message: " + err.Error()) -// return -// } -// data.State = ConsensusPrepared -// } -//} -// -//func (pcm *PBFTConsensusManager) handleCommitMessage(sender peer.ID, message *models.Message) { -// // TODO add check on view of the message -// // TODO add validation of data by hash to this stage -// consensusID := message.Payload["consensusID"].(string) -// if _, ok := pcm.Consensuses[consensusID]; !ok { -// logrus.Warn("Unknown consensus ID: " + consensusID) -// return -// } -// data := pcm.Consensuses[consensusID] -// -// data.mutex.Lock() -// defer data.mutex.Unlock() -// if data.State == ConsensusCommitted { -// logrus.Debug("consensus already finished, dropping COMMIT message") -// return -// } -// -// logrus.Debug("received commit msg") -// -// signStr := message.Payload["signature"].(string) -// signRaw := []byte(signStr) -// err := sigs.Verify(&types.Signature{Data: signRaw, Type: types.SigTypeEd25519}, sender, message.Payload["data"].([]byte)) -// if err != nil { -// logrus.Warn("failed to verify data signature") -// return -// } -// -// data.commitCount++ -// -// if data.commitCount > 2*pcm.maxFaultNodes+1 { -// data.State = ConsensusCommitted -// data.result = message.Payload["data"].(string) -// logrus.Debug("consensus successfully finished with result: " + data.result) -// data.onConsensusFinishCallback(data.result) -// } -//} - -func (pcm *PBFTConsensusManager) Propose(consensusID, data string, requestID *big.Int, callbackAddress common.Address) error { +func (pcm *PBFTConsensusManager) Propose(consensusID string, task types2.DioneTask, requestID *big.Int, callbackAddress common.Address) error { pcm.consensusInfo[consensusID] = &ConsensusData{} reqIDRaw := requestID.String() callbackAddressHex := callbackAddress.Hex() - prePrepareMsg, err := pcm.prePreparePool.CreatePrePrepare(consensusID, data, reqIDRaw, callbackAddressHex, pcm.privKey) + prePrepareMsg, err := pcm.prePreparePool.CreatePrePrepare(consensusID, task, reqIDRaw, callbackAddressHex, pcm.privKey) if err != nil { return err } @@ -210,13 +72,7 @@ func (pcm *PBFTConsensusManager) handlePrePrepare(message *types.Message) { } pcm.prePreparePool.AddPrePrepare(message) - - err := pcm.resignMessage(message) - if err != nil { - logrus.Errorf(err.Error()) - return - } - err = pcm.psb.BroadcastToServiceTopic(message) + err := pcm.psb.BroadcastToServiceTopic(message) if err != nil { logrus.Errorf(err.Error()) return @@ -240,12 +96,7 @@ func (pcm *PBFTConsensusManager) handlePrepare(message *types.Message) { } pcm.preparePool.AddPrepare(message) - err := pcm.resignMessage(message) - if err != nil { - logrus.Errorf(err.Error()) - return - } - err = pcm.psb.BroadcastToServiceTopic(message) + err := pcm.psb.BroadcastToServiceTopic(message) if err != nil { logrus.Errorf(err.Error()) return @@ -271,12 +122,7 @@ func (pcm *PBFTConsensusManager) handleCommit(message *types.Message) { } pcm.commitPool.AddCommit(message) - err := pcm.resignMessage(message) - if err != nil { - logrus.Errorf(err.Error()) - return - } - err = pcm.psb.BroadcastToServiceTopic(message) + err := pcm.psb.BroadcastToServiceTopic(message) if err != nil { logrus.Errorf(err.Error()) return @@ -296,7 +142,7 @@ func (pcm *PBFTConsensusManager) handleCommit(message *types.Message) { logrus.Errorf("Failed to parse big int: %v", consensusMsg.RequestID) } callbackAddress := common.HexToAddress(consensusMsg.CallbackAddress) - err := pcm.ethereumClient.SubmitRequestAnswer(reqID, consensusMsg.Data, callbackAddress) + err := pcm.ethereumClient.SubmitRequestAnswer(reqID, string(consensusMsg.Task.Payload), callbackAddress) if err != nil { logrus.Errorf("Failed to submit on-chain result: %w", err) } @@ -304,12 +150,3 @@ func (pcm *PBFTConsensusManager) handleCommit(message *types.Message) { } } } - -func (pcm *PBFTConsensusManager) resignMessage(msg *types.Message) error { - sig, err := sigs.Sign(types2.SigTypeEd25519, pcm.privKey, []byte(msg.Payload.Data)) - if err != nil { - return err - } - msg.Payload.Signature = sig.Data - return nil -} diff --git a/consensus/leader.go b/consensus/leader.go index 6f53b7d..3f1f4d2 100644 --- a/consensus/leader.go +++ b/consensus/leader.go @@ -1,9 +1,10 @@ package consensus import ( - "context" "fmt" + "github.com/Secured-Finance/dione/sigs" + "github.com/libp2p/go-libp2p-core/peer" "github.com/Secured-Finance/dione/types" @@ -11,10 +12,10 @@ import ( "golang.org/x/xerrors" ) -type SignFunc func(context.Context, peer.ID, []byte) (*types.Signature, error) +type SignFunc func(peer.ID, []byte) (*types.Signature, error) -func ComputeVRF(ctx context.Context, sign SignFunc, worker peer.ID, sigInput []byte) ([]byte, error) { - sig, err := sign(ctx, worker, sigInput) +func ComputeVRF(sign SignFunc, worker peer.ID, sigInput []byte) ([]byte, error) { + sig, err := sign(worker, sigInput) if err != nil { return nil, err } @@ -26,22 +27,17 @@ func ComputeVRF(ctx context.Context, sign SignFunc, worker peer.ID, sigInput []b return sig.Data, nil } -func VerifyVRF(ctx context.Context, worker peer.ID, vrfBase, vrfproof []byte) error { - pKey, err := worker.ExtractPublicKey() +func VerifyVRF(worker peer.ID, vrfBase, vrfproof []byte) error { + err := sigs.Verify(&types.Signature{Type: types.SigTypeEd25519, Data: vrfproof}, worker, vrfBase) if err != nil { - return xerrors.Errorf("failed to extract public key from worker address: %w", err) - } - - valid, err := pKey.Verify(vrfBase, vrfproof) - if err != nil || !valid { return xerrors.Errorf("vrf was invalid: %w", err) } return nil } -func IsRoundWinner(ctx context.Context, round types.DrandRound, - worker peer.ID, brand types.BeaconEntry, minerStake, networkStake types.BigInt, a WalletAPI) (*types.ElectionProof, error) { +func IsRoundWinner(round types.DrandRound, + worker peer.ID, brand types.BeaconEntry, minerStake, networkStake types.BigInt, sign SignFunc) (*types.ElectionProof, error) { buf, err := worker.MarshalBinary() if err != nil { @@ -53,7 +49,7 @@ func IsRoundWinner(ctx context.Context, round types.DrandRound, return nil, xerrors.Errorf("failed to draw randomness: %w", err) } - vrfout, err := ComputeVRF(ctx, a.WalletSign, worker, electionRand) + vrfout, err := ComputeVRF(sign, worker, electionRand) if err != nil { return nil, xerrors.Errorf("failed to compute VRF: %w", err) } diff --git a/consensus/miner.go b/consensus/miner.go index 20f1c24..313f0ae 100644 --- a/consensus/miner.go +++ b/consensus/miner.go @@ -4,6 +4,10 @@ import ( "context" "sync" + big2 "github.com/filecoin-project/go-state-types/big" + + "github.com/Secured-Finance/dione/sigs" + "github.com/Secured-Finance/dione/rpc" "github.com/Secured-Finance/dione/beacon" @@ -21,34 +25,30 @@ import ( type Miner struct { address peer.ID ethAddress common.Address - api WalletAPI mutex sync.Mutex beacon beacon.BeaconNetworks ethClient *ethclient.EthereumClient minerStake types.BigInt networkStake types.BigInt + privateKey []byte } func NewMiner( address peer.ID, ethAddress common.Address, - api WalletAPI, beacon beacon.BeaconNetworks, ethClient *ethclient.EthereumClient, + privateKey []byte, ) *Miner { return &Miner{ address: address, ethAddress: ethAddress, - api: api, beacon: beacon, ethClient: ethClient, + privateKey: privateKey, } } -type WalletAPI interface { - WalletSign(context.Context, peer.ID, []byte) (*types.Signature, error) -} - func (m *Miner) UpdateCurrentStakeInfo() error { mStake, err := m.ethClient.GetMinerStake(m.ethAddress) @@ -70,25 +70,52 @@ func (m *Miner) UpdateCurrentStakeInfo() error { return nil } -func (m *Miner) MineTask(ctx context.Context, event *oracleEmitter.OracleEmitterNewOracleRequest, sign SignFunc) (*types.DioneTask, error) { - bvals, err := beacon.BeaconEntriesForTask(ctx, m.beacon) +func (m *Miner) GetStakeInfo(miner common.Address) (*types.BigInt, *types.BigInt, error) { + mStake, err := m.ethClient.GetMinerStake(miner) + + if err != nil { + logrus.Warn("Can't get miner stake", err) + return nil, nil, err + } + + nStake, err := m.ethClient.GetTotalStake() + + if err != nil { + logrus.Warn("Can't get miner stake", err) + return nil, nil, err + } + + return mStake, nStake, nil +} + +func (m *Miner) MineTask(ctx context.Context, event *oracleEmitter.OracleEmitterNewOracleRequest) (*types.DioneTask, error) { + beaconValues, err := beacon.BeaconEntriesForTask(ctx, m.beacon) if err != nil { return nil, xerrors.Errorf("failed to get beacon entries: %w", err) } - logrus.Debug("attempting to mine the task at epoch: ", bvals[1].Round) + logrus.Debug("attempting to mine the task at epoch: ", beaconValues[1].Round) - rbase := bvals[1] + randomBase := beaconValues[1] if err := m.UpdateCurrentStakeInfo(); err != nil { return nil, xerrors.Errorf("failed to update miner stake: %w", err) } - ticket, err := m.computeTicket(ctx, &rbase) + ticket, err := m.computeTicket(&randomBase) if err != nil { return nil, xerrors.Errorf("scratching ticket failed: %w", err) } - winner, err := IsRoundWinner(ctx, types.DrandRound(rbase.Round), m.address, rbase, m.minerStake, m.networkStake, m.api) + winner, err := IsRoundWinner( + types.DrandRound(randomBase.Round), + m.address, + randomBase, + m.minerStake, + m.networkStake, + func(id peer.ID, bytes []byte) (*types.Signature, error) { + return sigs.Sign(types.SigTypeEd25519, m.privateKey, bytes) + }, + ) if err != nil { return nil, xerrors.Errorf("failed to check if we win next round: %w", err) } @@ -106,22 +133,22 @@ func (m *Miner) MineTask(ctx context.Context, event *oracleEmitter.OracleEmitter return nil, xerrors.Errorf("couldn't do rpc request: %w", err) } bres := []byte(res) - signature, err := sign(ctx, m.address, bres) - if err != nil { - return nil, xerrors.Errorf("Couldn't sign solana response: %w", err) - } return &types.DioneTask{ + OriginChain: event.OriginChain, + RequestType: event.RequestType, + RequestParams: event.RequestParams, + Miner: m.address, + MinerEth: m.ethAddress.Hex(), Ticket: ticket, ElectionProof: winner, - BeaconEntries: bvals, + BeaconEntries: beaconValues, Payload: bres, - Signature: signature, - DrandRound: types.DrandRound(rbase.Round), + DrandRound: types.DrandRound(randomBase.Round), }, nil } -func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry) (*types.Ticket, error) { +func (m *Miner) computeTicket(brand *types.BeaconEntry) (*types.Ticket, error) { buf, err := m.address.MarshalBinary() if err != nil { return nil, xerrors.Errorf("failed to marshal address: %w", err) @@ -134,7 +161,9 @@ func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry) (*t return nil, err } - vrfOut, err := ComputeVRF(ctx, m.api.WalletSign, m.address, input) + vrfOut, err := ComputeVRF(func(id peer.ID, bytes []byte) (*types.Signature, error) { + return sigs.Sign(types.SigTypeEd25519, m.privateKey, bytes) + }, m.address, input) if err != nil { return nil, err } @@ -143,3 +172,15 @@ func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry) (*t VRFProof: vrfOut, }, nil } + +func (m *Miner) IsMinerEligibleToProposeTask(ethAddress common.Address) error { + mStake, err := m.ethClient.GetMinerStake(ethAddress) + if err != nil { + return err + } + ok := mStake.GreaterThanEqual(big2.NewInt(ethclient.MinMinerStake)) + if !ok { + return xerrors.Errorf("miner doesn't have enough staked tokens") + } + return nil +} diff --git a/consensus/pre_prepare_pool.go b/consensus/pre_prepare_pool.go index fc00000..f67d3c2 100644 --- a/consensus/pre_prepare_pool.go +++ b/consensus/pre_prepare_pool.go @@ -1,31 +1,44 @@ package consensus import ( + "fmt" + + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/ethereum/go-ethereum/common" + types2 "github.com/Secured-Finance/dione/consensus/types" "github.com/Secured-Finance/dione/sigs" "github.com/Secured-Finance/dione/types" + "github.com/mitchellh/hashstructure/v2" "github.com/sirupsen/logrus" ) type PrePreparePool struct { prePrepareMsgs map[string][]*types2.Message + miner *Miner } -func NewPrePreparePool() *PrePreparePool { +func NewPrePreparePool(miner *Miner) *PrePreparePool { return &PrePreparePool{ prePrepareMsgs: map[string][]*types2.Message{}, + miner: miner, } } -func (pp *PrePreparePool) CreatePrePrepare(consensusID, data string, requestID string, callbackAddress string, privateKey []byte) (*types2.Message, error) { +func (pp *PrePreparePool) CreatePrePrepare(consensusID string, task types.DioneTask, requestID string, callbackAddress string, privateKey []byte) (*types2.Message, error) { var message types2.Message message.Type = types2.MessageTypePrePrepare var consensusMsg types2.ConsensusMessage consensusMsg.ConsensusID = consensusID consensusMsg.RequestID = requestID consensusMsg.CallbackAddress = callbackAddress - consensusMsg.Data = data - signature, err := sigs.Sign(types.SigTypeEd25519, privateKey, []byte(data)) + consensusMsg.Task = task + cHash, err := hashstructure.Hash(consensusMsg, hashstructure.FormatV2, nil) + if err != nil { + return nil, err + } + signature, err := sigs.Sign(types.SigTypeEd25519, privateKey, []byte(fmt.Sprintf("%v", cHash))) if err != nil { return nil, err } @@ -48,11 +61,83 @@ func (ppp *PrePreparePool) IsExistingPrePrepare(prepareMsg *types2.Message) bool func (ppp *PrePreparePool) IsValidPrePrepare(prePrepare *types2.Message) bool { // TODO here we need to do validation of tx itself consensusMsg := prePrepare.Payload - err := sigs.Verify(&types.Signature{Type: types.SigTypeEd25519, Data: consensusMsg.Signature}, prePrepare.From, []byte(consensusMsg.Data)) + + // === verify task signature === + err := verifyTaskSignature(consensusMsg) if err != nil { logrus.Errorf("unable to verify signature: %v", err) return false } + ///////////////////////////////// + + // === verify election proof wincount preliminarily === + if consensusMsg.Task.ElectionProof.WinCount < 1 { + logrus.Error("miner isn't a winner!") + return false + } + ///////////////////////////////// + + // === verify miner's eligibility to propose this task === + err = ppp.miner.IsMinerEligibleToProposeTask(common.HexToAddress(consensusMsg.Task.MinerEth)) + if err != nil { + logrus.Errorf("miner is not eligible to propose task: %v", err) + return false + } + ///////////////////////////////// + + // === verify election proof vrf === + minerAddressMarshalled, err := prePrepare.Payload.Task.Miner.MarshalBinary() + if err != nil { + logrus.Errorf("failed to marshal miner address: %v", err) + return false + } + electionProofRandomness, err := DrawRandomness( + consensusMsg.Task.BeaconEntries[1].Data, + crypto.DomainSeparationTag_ElectionProofProduction, + consensusMsg.Task.DrandRound, + minerAddressMarshalled, + ) + if err != nil { + logrus.Errorf("failed to draw electionProofRandomness: %v", err) + return false + } + err = VerifyVRF(consensusMsg.Task.Miner, electionProofRandomness, consensusMsg.Task.ElectionProof.VRFProof) + if err != nil { + logrus.Errorf("failed to verify election proof vrf: %v", err) + } + ////////////////////////////////////// + + // === verify ticket vrf === + ticketRandomness, err := DrawRandomness( + consensusMsg.Task.BeaconEntries[1].Data, + crypto.DomainSeparationTag_TicketProduction, + consensusMsg.Task.DrandRound-types.TicketRandomnessLookback, + minerAddressMarshalled, + ) + if err != nil { + logrus.Errorf("failed to draw ticket electionProofRandomness: %v", err) + return false + } + + err = VerifyVRF(consensusMsg.Task.Miner, ticketRandomness, consensusMsg.Task.Ticket.VRFProof) + if err != nil { + logrus.Errorf("failed to verify ticket vrf: %v", err) + } + ////////////////////////////////////// + + // === compute wincount locally and verify values === + mStake, nStake, err := ppp.miner.GetStakeInfo(common.HexToAddress(consensusMsg.Task.MinerEth)) + if err != nil { + logrus.Errorf("failed to get miner stake: %v", err) + return false + } + actualWinCount := consensusMsg.Task.ElectionProof.ComputeWinCount(*mStake, *nStake) + if consensusMsg.Task.ElectionProof.WinCount != actualWinCount { + logrus.Errorf("locally computed wincount isn't matching received value!", err) + return false + } + ////////////////////////////////////// + return true } diff --git a/consensus/prepare_pool.go b/consensus/prepare_pool.go index 8580c15..fb53dbe 100644 --- a/consensus/prepare_pool.go +++ b/consensus/prepare_pool.go @@ -2,8 +2,6 @@ package consensus import ( types2 "github.com/Secured-Finance/dione/consensus/types" - "github.com/Secured-Finance/dione/sigs" - "github.com/Secured-Finance/dione/types" ) type PreparePool struct { @@ -20,18 +18,8 @@ func NewPreparePool() *PreparePool { func (pp *PreparePool) CreatePrepare(prePrepareMsg *types2.Message, privateKey []byte) (*types2.Message, error) { var message types2.Message message.Type = types2.MessageTypePrepare - var consensusMsg types2.ConsensusMessage - prepareCMessage := prePrepareMsg.Payload - consensusMsg.ConsensusID = prepareCMessage.ConsensusID - consensusMsg.RequestID = prePrepareMsg.Payload.RequestID - consensusMsg.CallbackAddress = prePrepareMsg.Payload.CallbackAddress - consensusMsg.Data = prepareCMessage.Data - signature, err := sigs.Sign(types.SigTypeEd25519, privateKey, []byte(prepareCMessage.Data)) - if err != nil { - return nil, err - } - consensusMsg.Signature = signature.Data - message.Payload = consensusMsg + newCMsg := prePrepareMsg.Payload + message.Payload = newCMsg return &message, nil } @@ -48,7 +36,7 @@ func (pp *PreparePool) IsExistingPrepare(prepareMsg *types2.Message) bool { func (pp *PreparePool) IsValidPrepare(prepare *types2.Message) bool { consensusMsg := prepare.Payload - err := sigs.Verify(&types.Signature{Type: types.SigTypeEd25519, Data: consensusMsg.Signature}, prepare.From, []byte(consensusMsg.Data)) + err := verifyTaskSignature(consensusMsg) if err != nil { return false } diff --git a/consensus/types/message.go b/consensus/types/message.go index b2ea217..ce3a87a 100644 --- a/consensus/types/message.go +++ b/consensus/types/message.go @@ -1,6 +1,7 @@ package types import ( + "github.com/Secured-Finance/dione/types" "github.com/libp2p/go-libp2p-core/peer" ) @@ -15,12 +16,12 @@ const ( ) type ConsensusMessage struct { - _ struct{} `cbor:",toarray"` + _ struct{} `cbor:",toarray" hash:"-"` ConsensusID string - Signature []byte + Signature []byte `hash:"-"` RequestID string CallbackAddress string - Data string + Task types.DioneTask } type Message struct { diff --git a/consensus/utils.go b/consensus/utils.go new file mode 100644 index 0000000..1c247f3 --- /dev/null +++ b/consensus/utils.go @@ -0,0 +1,26 @@ +package consensus + +import ( + "fmt" + + "github.com/Secured-Finance/dione/consensus/types" + "github.com/Secured-Finance/dione/sigs" + types2 "github.com/Secured-Finance/dione/types" + "github.com/mitchellh/hashstructure/v2" +) + +func verifyTaskSignature(msg types.ConsensusMessage) error { + cHash, err := hashstructure.Hash(msg, hashstructure.FormatV2, nil) + if err != nil { + return err + } + err = sigs.Verify( + &types2.Signature{Type: types2.SigTypeEd25519, Data: msg.Signature}, + msg.Task.Miner, + []byte(fmt.Sprintf("%v", cHash)), + ) + if err != nil { + return err + } + return nil +} diff --git a/ethclient/stake.go b/ethclient/stake.go index 0e3b6d5..c7d36d8 100644 --- a/ethclient/stake.go +++ b/ethclient/stake.go @@ -5,6 +5,10 @@ import ( "github.com/ethereum/go-ethereum/common" ) +const ( + MinMinerStake = 1000 +) + // Getting total stake in DioneStaking contract, this function could // be used for storing the total stake and veryfing the stake tokens // on new tasks diff --git a/go.mod b/go.mod index 51e318b..3c196c6 100644 --- a/go.mod +++ b/go.mod @@ -44,11 +44,12 @@ require ( github.com/mattn/go-colorable v0.1.6 // indirect github.com/mattn/go-sqlite3 v1.9.0 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 + github.com/mitchellh/hashstructure/v2 v2.0.1 github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/multiformats/go-multiaddr v0.3.1 github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/onsi/ginkgo v1.14.0 // indirect - github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a + github.com/polydawn/refmt v0.0.0-20190809202753-05966cbd336a // indirect github.com/raulk/clock v1.1.0 github.com/rjeczalik/notify v0.9.2 // indirect github.com/rs/cors v1.7.0 // indirect diff --git a/go.sum b/go.sum index 72719e3..7355ee2 100644 --- a/go.sum +++ b/go.sum @@ -208,7 +208,6 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg= github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= @@ -764,6 +763,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure/v2 v2.0.1 h1:L60q1+q7cXE4JeEJJKMnh2brFIe3rZxCihYAB61ypAY= +github.com/mitchellh/hashstructure/v2 v2.0.1/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= diff --git a/node/ethereum.go b/node/ethereum.go index ae2c387..277586c 100644 --- a/node/ethereum.go +++ b/node/ethereum.go @@ -9,7 +9,7 @@ import ( func (n *Node) subscribeOnEthContracts(ctx context.Context) { eventChan, subscription, err := n.Ethereum.SubscribeOnOracleEvents(ctx) if err != nil { - logrus.Fatal("Can't subscribe on ethereum contracts, exiting... ", err) + logrus.Fatal("Couldn't subscribe on ethereum contracts, exiting... ", err) } go func() { @@ -18,16 +18,15 @@ func (n *Node) subscribeOnEthContracts(ctx context.Context) { select { case event := <-eventChan: { - task, err := n.Miner.MineTask(ctx, event, n.Wallet.WalletSign) + task, err := n.Miner.MineTask(ctx, event) if err != nil { - logrus.Fatal("Error with mining algorithm, exiting... ", err) + logrus.Fatal("Failed to mine task, exiting... ", err) } if task == nil { continue } - logrus.Info("Started new consensus round with ID: ", task.Signature) - - err = n.ConsensusManager.Propose(event.RequestID.String(), string(task.Payload), event.RequestID, event.CallbackAddress) + logrus.Infof("Started new consensus round with ID: %s", event.RequestID.String()) + err = n.ConsensusManager.Propose(event.RequestID.String(), *task, event.RequestID, event.CallbackAddress) if err != nil { logrus.Errorf("Failed to propose task: %w", err) } diff --git a/node/node.go b/node/node.go index 4e30e5c..553a6bb 100644 --- a/node/node.go +++ b/node/node.go @@ -81,27 +81,31 @@ func (n *Node) setupNode(ctx context.Context, prvKey crypto.PrivKey, pexDiscover } n.setupPubsub() - err = n.setupConsensusManager(prvKey, n.Config.ConsensusMinApprovals) + rawPrivKey, err := prvKey.Raw() if err != nil { - logrus.Fatalf("Failed to setup consensus manager: %w", err) + logrus.Fatal(err) } err = n.setupBeacon() if err != nil { logrus.Fatal(err) } - err = n.setupWallet(prvKey) + err = n.setupMiner(rawPrivKey) if err != nil { logrus.Fatal(err) } - err = n.setupMiner() + err = n.setupConsensusManager(rawPrivKey, n.Config.ConsensusMinApprovals) + if err != nil { + logrus.Fatalf("Failed to setup consensus manager: %w", err) + } + err = n.setupWallet(rawPrivKey) if err != nil { logrus.Fatal(err) } n.subscribeOnEthContracts(ctx) } -func (n *Node) setupMiner() error { - n.Miner = consensus.NewMiner(n.Host.ID(), *n.Ethereum.GetEthAddress(), n.Wallet, n.Beacon, n.Ethereum) +func (n *Node) setupMiner(privKey []byte) error { + n.Miner = consensus.NewMiner(n.Host.ID(), *n.Ethereum.GetEthAddress(), n.Beacon, n.Ethereum, privKey) return nil } @@ -114,16 +118,12 @@ func (n *Node) setupBeacon() error { return nil } -func (n *Node) setupWallet(privKey crypto.PrivKey) error { +func (n *Node) setupWallet(privKey []byte) error { // TODO make persistent keystore kstore := wallet.NewMemKeyStore() - pKeyBytes, err := privKey.Raw() - if err != nil { - return xerrors.Errorf("failed to get raw private key: %w", err) - } keyInfo := types.KeyInfo{ Type: types.KTEd25519, - PrivateKey: pKeyBytes, + PrivateKey: privKey, } kstore.Put(wallet.KNamePrefix+n.Host.ID().String(), keyInfo) @@ -167,12 +167,8 @@ func (n *Node) setupPubsub() { //time.Sleep(3 * time.Second) } -func (n *Node) setupConsensusManager(privateKey crypto.PrivKey, minApprovals int) error { - pkeyRaw, err := privateKey.Raw() - if err != nil { - return err - } - n.ConsensusManager = consensus.NewPBFTConsensusManager(n.PubSubRouter, minApprovals, pkeyRaw, n.Ethereum) +func (n *Node) setupConsensusManager(privateKey []byte, minApprovals int) error { + n.ConsensusManager = consensus.NewPBFTConsensusManager(n.PubSubRouter, minApprovals, privateKey, n.Ethereum, n.Miner) return nil } diff --git a/types/task.go b/types/task.go index ca48994..6d3dc7a 100644 --- a/types/task.go +++ b/types/task.go @@ -8,15 +8,7 @@ import ( "github.com/Secured-Finance/dione/config" ) -type TaskType byte - -const ( - EthereumTaskType = TaskType(iota) - FilecoinTaskType - SolanaTaskType -) - -// DrandRound represents the round number in DRAND +// DrandRound represents the round number in DRAND type DrandRound int64 const TicketRandomnessLookback = DrandRound(1) @@ -25,36 +17,40 @@ func (e DrandRound) String() string { return strconv.FormatInt(int64(e), 10) } -// DioneTask represents the values of task computation -// Miner is an address of miner node +// DioneTask represents the values of task computation type DioneTask struct { + _ struct{} `cbor:",toarray" hash:"-"` + OriginChain uint8 + RequestType string + RequestParams string Miner peer.ID - Type TaskType + MinerEth string Ticket *Ticket ElectionProof *ElectionProof BeaconEntries []BeaconEntry - Signature *Signature DrandRound DrandRound Payload []byte } func NewDioneTask( - t TaskType, + originChain uint8, + requestType string, + requestParams string, miner peer.ID, ticket *Ticket, electionProof *ElectionProof, beacon []BeaconEntry, - sig *Signature, drand DrandRound, payload []byte, ) *DioneTask { return &DioneTask{ - Type: t, + OriginChain: originChain, + RequestType: requestType, + RequestParams: requestParams, Miner: miner, Ticket: ticket, ElectionProof: electionProof, BeaconEntries: beacon, - Signature: sig, DrandRound: drand, Payload: payload, }