diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index dbefa0c532..a896dcfbbd 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -528,7 +528,7 @@ func (p *Parlia) snapshot(chain consensus.ChainHeaderReader, number uint64, hash return nil, err } - // new snap shot + // new snapshot snap = newSnapshot(p.config, p.signatures, number, hash, validators, voteAddrs, p.ethAPI) if err := snap.store(p.db); err != nil { return nil, err @@ -682,7 +682,7 @@ func (p *Parlia) PrepareVoteAttestation(chain consensus.ChainHeaderReader, heade return nil } - var attestation types.VoteAttestation + var attestation *types.VoteEnvelope // TODO: Add a simple vote aggregation from votePool for test, I will modify this to match the new finality rules. buf := new(bytes.Buffer) @@ -776,6 +776,73 @@ func (p *Parlia) verifyValidators(header *types.Header) error { return nil } +func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header, + cx core.ChainContext, txs *[]*types.Transaction, receipts *[]*types.Receipt, systemTxs *[]*types.Transaction, + usedGas *uint64, mining bool) error { + currentHeight := header.Number.Uint64() + epoch := p.config.Epoch + chainConfig := chain.Config() + if currentHeight%epoch != 0 || !chainConfig.IsBoneh(header.Number) { + return nil + } + + accumulatedWeights := make(map[common.Address]uint64) + for height := currentHeight - epoch; height <= currentHeight; height++ { + head := chain.GetHeaderByNumber(height) + if head == nil { + continue + } + voteAttestation, err := getVoteAttestationFromHeader(head, chainConfig, p.config) + if err != nil { + return err + } + justifiedBlock := chain.GetHeaderByHash(voteAttestation.Data.BlockHash) + if justifiedBlock == nil { + continue + } + rewardCoef := uint64(1) + switch height - justifiedBlock.Number.Uint64() { + case 1: + rewardCoef = 8 + case 2: + rewardCoef = 4 + } + snap, err := p.snapshot(chain, height, head.Hash(), nil) + if err != nil { + return err + } + validators := snap.validators() + for j := 0; j < 64; j++ { + if ((uint64(voteAttestation.VoteAddressSet) >> j) & 1) == 1 { + accumulatedWeights[validators[j]] += rewardCoef + } + } + } + validators := make([]common.Address, 0, len(accumulatedWeights)) + weights := make([]uint64, 0, len(accumulatedWeights)) + for val, weight := range accumulatedWeights { + validators = append(validators, val) + weights = append(weights, weight) + } + + // method + method := "distributeFinalityReward" + + // get packed data + data, err := p.validatorSetABI.Pack(method, + validators, + weights, + ) + if err != nil { + log.Error("Unable to pack tx for distributeFinalityReward", "error", err) + return err + } + // get system message + msg := p.getSystemMessage(header.Coinbase, common.HexToAddress(systemcontracts.ValidatorContract), data, common.Big0) + // apply message + return p.applyTransaction(msg, state, header, cx, txs, receipts, systemTxs, usedGas, mining) +} + // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given. func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs *[]*types.Transaction, @@ -790,14 +857,20 @@ func (p *Parlia) Finalize(chain consensus.ChainHeaderReader, header *types.Heade if !snap.isMajorityFork(hex.EncodeToString(nextForkHash[:])) { log.Debug("there is a possible fork, and your client is not the majority. Please check...", "nextForkHash", hex.EncodeToString(nextForkHash[:])) } - // If the block is a epoch end block, verify the validator list + // If the block is an epoch end block, verify the validator list // The verification can only be done when the state is ready, it can't be done in VerifyHeader. if err := p.verifyValidators(header); err != nil { return err } - // No block rewards in PoA, so the state remains as is and uncles are dropped + // If the block is an epoch end block, distribute the finality reward + // The distribution can only be done when the state is ready, it can't be done in VerifyHeader. cx := chainContext{Chain: chain, parlia: p} + if err := p.distributeFinalityReward(chain, state, header, cx, txs, receipts, systemTxs, usedGas, false); err != nil { + return err + } + + // No block rewards in PoA, so the state remains as is and uncles are dropped if header.Number.Cmp(common.Big1) == 0 { err := p.initContract(state, header, cx, txs, receipts, systemTxs, usedGas, false) if err != nil { @@ -1120,14 +1193,14 @@ func (p *Parlia) getCurrentValidators(blockHash common.Hash) ([]common.Address, blockNr := rpc.BlockNumberOrHashWithHash(blockHash, false) // method - method := "getValidators" + method := "getMiningValidators" ctx, cancel := context.WithCancel(context.Background()) defer cancel() // cancel when we are finished consuming integers data, err := p.validatorSetABI.Pack(method) if err != nil { - log.Error("Unable to pack tx for getValidators", "error", err) + log.Error("Unable to pack tx for getMiningValidators", "error", err) return nil, nil, err } // call @@ -1145,20 +1218,24 @@ func (p *Parlia) getCurrentValidators(blockHash common.Hash) ([]common.Address, var ( ret0 = new([]common.Address) + ret1 = new([]types.BLSPublicKey) ) - out := ret0 + out := struct { + consensusAddrs []common.Address + voteAddrs []types.BLSPublicKey + }{*ret0, *ret1} if err := p.validatorSetABI.UnpackIntoInterface(out, method, result); err != nil { return nil, nil, err } valz := make([]common.Address, len(*ret0)) - for i, a := range *ret0 { - valz[i] = a + voteAddrmap := make(map[common.Address]types.BLSPublicKey) + for i := 0; i < len(*ret0); i++ { + valz[i] = (*ret0)[i] + voteAddrmap[(*ret0)[i]] = (*ret1)[i] } - return valz, nil, nil - - // TODO: return vote address after boneh fork. + return valz, voteAddrmap, nil } // slash spoiled validators diff --git a/core/types/vote.go b/core/types/vote.go index aa6e6b6d30..771d819cbe 100644 --- a/core/types/vote.go +++ b/core/types/vote.go @@ -15,9 +15,6 @@ type BLSPublicKey [BLSPublicKeyLength]byte type BLSSignature [BLSSignatureLength]byte type ValidatorsBitSet uint64 -// Bytes gets the string representation of the underlying BLS public key. -func (p BLSPublicKey) Bytes() []byte { return p[:] } - type VoteData struct { BlockNumber uint64 BlockHash common.Hash @@ -26,13 +23,18 @@ type VoteData struct { type VoteEnvelope struct { VoteAddress BLSPublicKey Signature BLSSignature - Data VoteData + Data *VoteData // caches hash atomic.Value } -type VoteEnvelopes []*VoteEnvelope +type VoteAttestation struct { + VoteAddressSet ValidatorsBitSet + AggSignature BLSSignature + Data *VoteData + Extra []byte +} // Hash returns the vote hash. func (v *VoteEnvelope) Hash() common.Hash { @@ -49,14 +51,11 @@ func (v *VoteEnvelope) calcVoteHash() common.Hash { voteData := struct { VoteAddress BLSPublicKey Signature BLSSignature - Data VoteData + Data *VoteData }{v.VoteAddress, v.Signature, v.Data} return rlpHash(voteData) } -type VoteAttestation struct { - VoteAddressSet ValidatorsBitSet - AggSignature BLSSignature - Data VoteData - Extra []byte -} +func (b BLSPublicKey) Bytes() []byte { return b[:] } + +func (b BLSSignature) Bytes() []byte { return b[:] } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 07e125dbf7..4f5e9ad8b8 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -21,17 +21,22 @@ import ( "encoding/binary" "errors" "math/big" + "sync" + + //lint:ignore SA1019 Needed for precompile + "github.com/prysmaticlabs/prysm/crypto/bls" + "golang.org/x/crypto/ripemd160" + "golang.org/x/crypto/sha3" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bn256" "github.com/ethereum/go-ethereum/params" - - //lint:ignore SA1019 Needed for precompile - "golang.org/x/crypto/ripemd160" + "github.com/ethereum/go-ethereum/rlp" ) // PrecompiledContract is the basic interface for native Go contracts. The implementation @@ -93,6 +98,8 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, common.BytesToAddress([]byte{9}): &blake2F{}, + + common.BytesToAddress([]byte{100}): &finalitySignatureVerify{}, } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum @@ -1046,3 +1053,63 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // Encode the G2 point to 256 bytes return g.EncodePoint(r), nil } + +var errFinalitySignatureVerify = errors.New("invalid signatures") + +// finalitySignatureVerify implements BEP-126 finality signature verification precompile. +type finalitySignatureVerify struct{} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +func (c *finalitySignatureVerify) RequiredGas(input []byte) uint64 { + return params.SignatureVerifyGas +} + +func (c *finalitySignatureVerify) Run(input []byte) ([]byte, error) { + var ( + numA = new(big.Int).SetBytes(getData(input, 0, 32)).Uint64() + headerA = getData(input, 32, 32) + sigA = getData(input, 64, 96) + numB = new(big.Int).SetBytes(getData(input, 160, 32)).Uint64() + headerB = getData(input, 192, 32) + sigB = getData(input, 224, 96) + BLSKey = getData(input, 320, 48) + ) + + sigs := make([][]byte, 2) + msgs := make([][32]byte, 2) + pubKeys := make([]bls.PublicKey, 2) + + pubKey, err := bls.PublicKeyFromBytes(BLSKey) + if err != nil { + return nil, err + } + pubKeys[0] = pubKey + pubKeys[1] = pubKey + + msgs[0] = rlpHash(types.VoteData{BlockNumber: numA, BlockHash: common.BytesToHash(headerA)}) + msgs[1] = rlpHash(types.VoteData{BlockNumber: numB, BlockHash: common.BytesToHash(headerB)}) + sigs[0] = sigA + sigs[1] = sigB + + success, err := bls.VerifyMultipleSignatures(sigs, msgs, pubKeys) + if err != nil { + return nil, err + } + if !success { + return nil, errFinalitySignatureVerify + } + return big1.Bytes(), nil +} + +// rlpHash encodes x and hashes the encoded bytes. +func rlpHash(x interface{}) (h [32]byte) { + var hasherPool = sync.Pool{ + New: func() interface{} { return sha3.NewLegacyKeccak256() }, + } + sha := hasherPool.Get().(crypto.KeccakState) + defer hasherPool.Put(sha) + sha.Reset() + rlp.Encode(sha, x) + sha.Read(h[:]) + return h +} diff --git a/eth/handler.go b/eth/handler.go index 7f6e89cf35..0cd201ec43 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -87,9 +87,9 @@ type txPool interface { // votePool defines the methods needed from a votes pool implementation to // support all the operations needed by the Ethereum chain protocols. type votePool interface { - PutVote(vote *types.VoteEnvelope) error - Get(hash common.Hash) *types.VoteEnvelope - GetVotes() types.VoteEnvelopes + PutVote(vote *types.VoteEnvelope) + FetchAvailableVotes(blockHash common.Hash) []*types.VoteEnvelope + GetVotes() []*types.VoteEnvelope // SubscribeNewVotesEvent should return an event subscription of // NewVotesEvent and send events to the given channel. @@ -611,7 +611,7 @@ func (h *handler) ReannounceTransactions(txs types.Transactions) { // BroadcastVotes will propagate a batch of votes to all peers // which are not known to already have the given vote. -func (h *handler) BroadcastVotes(votes types.VoteEnvelopes) { +func (h *handler) BroadcastVotes(votes []*types.VoteEnvelope) { var ( directCount int // Count of announcements made directPeers int diff --git a/eth/handler_eth.go b/eth/handler_eth.go index be0858da73..d7d50ff848 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -235,9 +235,7 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td func (h *ethHandler) handleVotesBroadcast(peer *eth.Peer, votes []*types.VoteEnvelope) error { // Try to put votes into votepool for _, vote := range votes { - if err := h.votepool.PutVote(vote); err != nil { - return err - } + h.votepool.PutVote(vote) } return nil } diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index be7b2f2bdc..b4ab39ba8f 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -103,10 +103,7 @@ type TxPool interface { Get(hash common.Hash) *types.Transaction } -type VotePool interface { - // Get retrieves the vote from the local votepool with the given hash. - Get(hash common.Hash) *types.VoteEnvelope -} +type VotePool interface{} // MakeProtocols constructs the P2P protocol definitions for `eth`. func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index bd0071d224..cbb806323b 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -90,9 +90,9 @@ type Peer struct { txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests - votepool VotePool // Votes pool used by the broadcasters - knownVotes mapset.Set // Set of vote hashes known to be known by this peer - voteBroadcast chan types.VoteEnvelopes // Channel used to queue votes propagation requests + votepool VotePool // Votes pool used by the broadcasters + knownVotes mapset.Set // Set of vote hashes known to be known by this peer + voteBroadcast chan []*types.VoteEnvelope // Channel used to queue votes propagation requests term chan struct{} // Termination channel to stop the broadcasters txTerm chan struct{} // Termination channel to stop the tx broadcasters @@ -116,7 +116,7 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter, txpool TxPool, vot txBroadcast: make(chan []common.Hash), txAnnounce: make(chan []common.Hash), txpool: txpool, - voteBroadcast: make(chan types.VoteEnvelopes), + voteBroadcast: make(chan []*types.VoteEnvelope), votepool: votepool, term: make(chan struct{}), txTerm: make(chan struct{}), @@ -401,7 +401,7 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { } // SendVotes propagates a batch of votes to the remote peer. -func (p *Peer) SendVotes(votes types.VoteEnvelopes) error { +func (p *Peer) SendVotes(votes []*types.VoteEnvelope) error { // Mark all the transactions as known, but ensure we don't overflow our limits for p.knownVotes.Cardinality() > max(0, maxKnownTxs-len(votes)) { p.knownVotes.Pop() @@ -414,7 +414,7 @@ func (p *Peer) SendVotes(votes types.VoteEnvelopes) error { // AsyncSendVotes queues a batch of vote hashes for propagation to a remote peer. If // the peer's broadcast queue is full, the event is silently dropped. -func (p *Peer) AsyncSendVotes(votes types.VoteEnvelopes) { +func (p *Peer) AsyncSendVotes(votes []*types.VoteEnvelope) { select { case p.voteBroadcast <- votes: // Mark all the transactions as known, but ensure we don't overflow our limits diff --git a/params/protocol_params.go b/params/protocol_params.go index 84515869b6..abee195ade 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -117,13 +117,14 @@ const ( TendermintHeaderValidateGas uint64 = 3000 // Gas for validate tendermiint consensus state IAVLMerkleProofValidateGas uint64 = 3000 // Gas for validate merkle proof - EcrecoverGas uint64 = 3000 // Elliptic curve sender recovery gas price - Sha256BaseGas uint64 = 60 // Base price for a SHA256 operation - Sha256PerWordGas uint64 = 12 // Per-word price for a SHA256 operation - Ripemd160BaseGas uint64 = 600 // Base price for a RIPEMD160 operation - Ripemd160PerWordGas uint64 = 120 // Per-word price for a RIPEMD160 operation - IdentityBaseGas uint64 = 15 // Base price for a data copy operation - IdentityPerWordGas uint64 = 3 // Per-work price for a data copy operation + EcrecoverGas uint64 = 3000 // Elliptic curve sender recovery gas price + Sha256BaseGas uint64 = 60 // Base price for a SHA256 operation + Sha256PerWordGas uint64 = 12 // Per-word price for a SHA256 operation + Ripemd160BaseGas uint64 = 600 // Base price for a RIPEMD160 operation + Ripemd160PerWordGas uint64 = 120 // Per-word price for a RIPEMD160 operation + IdentityBaseGas uint64 = 15 // Base price for a data copy operation + IdentityPerWordGas uint64 = 3 // Per-work price for a data copy operation + SignatureVerifyGas uint64 = 70000 // Finality signature verify gas price Bn256AddGasByzantium uint64 = 500 // Byzantium gas needed for an elliptic curve addition Bn256AddGasIstanbul uint64 = 150 // Gas needed for an elliptic curve addition