|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/big"
|
|
|
|
"math/rand"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/harmony-one/harmony/core/rawdb"
|
|
|
|
|
|
|
|
msg_pb "github.com/harmony-one/harmony/api/proto/message"
|
|
|
|
"github.com/harmony-one/harmony/crypto/bls"
|
|
|
|
|
|
|
|
blockfactory "github.com/harmony-one/harmony/block/factory"
|
|
|
|
"github.com/harmony-one/harmony/internal/params"
|
|
|
|
"github.com/harmony-one/harmony/internal/utils"
|
|
|
|
|
|
|
|
common2 "github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
|
|
bls_core "github.com/harmony-one/bls/ffi/go/bls"
|
|
|
|
"github.com/harmony-one/harmony/core"
|
|
|
|
"github.com/harmony-one/harmony/core/state"
|
|
|
|
"github.com/harmony-one/harmony/core/vm"
|
|
|
|
"github.com/harmony-one/harmony/crypto/hash"
|
|
|
|
"github.com/harmony-one/harmony/internal/chain"
|
|
|
|
"github.com/harmony-one/harmony/internal/common"
|
|
|
|
|
|
|
|
protobuf "github.com/golang/protobuf/proto"
|
|
|
|
"github.com/harmony-one/harmony/numeric"
|
|
|
|
staking "github.com/harmony-one/harmony/staking/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
validatorAddress = common2.Address(common.MustBech32ToAddress("one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy"))
|
|
|
|
|
|
|
|
testBLSPubKey = "30b2c38b1316da91e068ac3bd8751c0901ef6c02a1d58bc712104918302c6ed03d5894671d0c816dad2b4d303320f202"
|
|
|
|
testBLSPrvKey = "c6d7603520311f7a4e6aac0b26701fc433b75b38df504cd416ef2b900cd66205"
|
|
|
|
postStakingEpoch = big.NewInt(200)
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
bls_core.Init(bls_core.BLS12_381)
|
|
|
|
}
|
|
|
|
|
|
|
|
func generateBLSKeySigPair() (bls.SerializedPublicKey, bls.SerializedSignature) {
|
|
|
|
p := &bls_core.PublicKey{}
|
|
|
|
p.DeserializeHexStr(testBLSPubKey)
|
|
|
|
pub := bls.SerializedPublicKey{}
|
|
|
|
pub.FromLibBLSPublicKey(p)
|
|
|
|
messageBytes := []byte(staking.BLSVerificationStr)
|
|
|
|
privateKey := &bls_core.SecretKey{}
|
|
|
|
privateKey.DeserializeHexStr(testBLSPrvKey)
|
|
|
|
msgHash := hash.Keccak256(messageBytes)
|
|
|
|
signature := privateKey.SignHash(msgHash[:])
|
|
|
|
var sig bls.SerializedSignature
|
|
|
|
copy(sig[:], signature.Serialize())
|
|
|
|
return pub, sig
|
|
|
|
}
|
|
|
|
|
|
|
|
func createValidator() *staking.CreateValidator {
|
|
|
|
desc := staking.Description{
|
|
|
|
Name: "SuperHero",
|
|
|
|
Identity: "YouWouldNotKnow",
|
|
|
|
Website: "Secret Website",
|
|
|
|
SecurityContact: "LicenseToKill",
|
|
|
|
Details: "blah blah blah",
|
|
|
|
}
|
|
|
|
rate, _ := numeric.NewDecFromStr("0.1")
|
|
|
|
maxRate, _ := numeric.NewDecFromStr("0.5")
|
|
|
|
maxChangeRate, _ := numeric.NewDecFromStr("0.05")
|
|
|
|
commission := staking.CommissionRates{
|
|
|
|
Rate: rate,
|
|
|
|
MaxRate: maxRate,
|
|
|
|
MaxChangeRate: maxChangeRate,
|
|
|
|
}
|
|
|
|
minSelfDel := new(big.Int).Mul(big.NewInt(5e18), big.NewInt(2000))
|
|
|
|
maxTotalDel := new(big.Int).Mul(big.NewInt(5e18), big.NewInt(100000))
|
|
|
|
pubKey, pubSig := generateBLSKeySigPair()
|
|
|
|
slotPubKeys := []bls.SerializedPublicKey{pubKey}
|
|
|
|
slotKeySigs := []bls.SerializedSignature{pubSig}
|
|
|
|
amount := new(big.Int).Mul(big.NewInt(5e18), big.NewInt(2000))
|
|
|
|
v := staking.CreateValidator{
|
|
|
|
ValidatorAddress: validatorAddress,
|
|
|
|
Description: desc,
|
|
|
|
CommissionRates: commission,
|
|
|
|
MinSelfDelegation: minSelfDel,
|
|
|
|
MaxTotalDelegation: maxTotalDel,
|
|
|
|
SlotPubKeys: slotPubKeys,
|
|
|
|
SlotKeySigs: slotKeySigs,
|
|
|
|
Amount: amount,
|
|
|
|
}
|
|
|
|
return &v
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
key, _ := crypto.GenerateKey()
|
|
|
|
gspec := core.Genesis{
|
|
|
|
Config: params.TestChainConfig,
|
|
|
|
Factory: blockfactory.ForTest,
|
|
|
|
Alloc: core.GenesisAlloc{
|
|
|
|
crypto.PubkeyToAddress(key.PublicKey): {
|
|
|
|
Balance: big.NewInt(8000000000000000000),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
GasLimit: 1e18,
|
|
|
|
ShardID: 0,
|
|
|
|
}
|
|
|
|
database := rawdb.NewMemoryDatabase()
|
|
|
|
genesis := gspec.MustCommit(database)
|
|
|
|
_ = genesis
|
|
|
|
engine := chain.NewEngine()
|
|
|
|
bc, _ := core.NewBlockChain(database, nil, nil, nil, gspec.Config, engine, vm.Config{})
|
|
|
|
statedb, _ := state.New(common2.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
|
|
|
msg := createValidator()
|
|
|
|
statedb.AddBalance(msg.ValidatorAddress, new(big.Int).Mul(big.NewInt(5e18), big.NewInt(2000)))
|
|
|
|
validator, err := core.VerifyAndCreateValidatorFromMsg(
|
|
|
|
statedb, bc, postStakingEpoch, big.NewInt(0), msg,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Print(err)
|
|
|
|
}
|
|
|
|
for i := 0; i < 100000; i++ {
|
|
|
|
validator.Delegations = append(validator.Delegations, staking.Delegation{
|
|
|
|
Amount: big.NewInt(int64(rand.Intn(100))),
|
|
|
|
Reward: big.NewInt(0),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
statedb.UpdateValidatorWrapper(msg.ValidatorAddress, validator)
|
|
|
|
|
|
|
|
startTime := time.Now()
|
Resolve harmony-one/bounties#90: Add revert mechanism for UpdateValidatorWrapper (#3939)
* Add revert mechanism for UpdateValidatorWrapper
Closes harmony-one/bounties#90
(1) Use LRU for ValidatorWrapper objects in stateDB to plug a potential
memory leak
(2) Merge ValidatorWrapper and ValidatorWrapperCopy to let callers ask
for either a copy, or a pointer to the cached object. Additionally,
give callers the option to not deep copy delegations (which is a
heavy process). Copies need to be explicitly committed (and thus
can be reverted), while the pointers are committed when Finalise
is called.
(3) Add a UpdateValidatorWrapperWithRevert function, which is used by
staking txs `Delegate`, `Undelegate`, and `CollectRewards`. Other
2 types of staking txs and `db.Finalize` continue to use
UpdateValidateWrapper without revert, again, to save memoery
(4) Add unit tests which check
a) Revert goes through
b) Wrapper is as expected after revert
c) State is as expected after revert
* Change back to dictionary for stateValidators
Since the memory / CPU usage saved is not significantly different when
using an LRU + map structure, go back to the original dictionary
structure to keep code easy to read and have limited modifications.
* Add tests for validator wrapper reverts
As requested by @rlan35, add tests beyond just adding and reverting a
delegation. The tests are successive in the sense that we do multiple
modifications to the wrapper, save a snapshot before each modification
and revert to each of them to confirm everything works well. This change
improves test coverage of statedb.go to 66.7% from 64.8% and that of
core/state to 71.9% from 70.8%, and covers all the code that has been
modified by this PR in statedb.go.
For clarity, the modifications to the wrapper include (1) creation of
wrapper in state, (2) adding a delegation to the wrapper, (3)
increasing the blocks signed, and (4) a change in the validator Name and
the BlockReward. Two additional tests have been added to cover the
`panic` and the `GetCode` cases.
3 years ago
|
|
|
validator, _ = statedb.ValidatorWrapper(msg.ValidatorAddress, true, false)
|
|
|
|
endTime := time.Now()
|
|
|
|
fmt.Printf("Time required to read validator: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
shares, _ := lookupDelegatorShares(validator)
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to calc percentage %d delegations: %f seconds\n", len(validator.Delegations), endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
statedb.AddReward(validator, big.NewInt(1000), shares)
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to reward a validator with %d delegations: %f seconds\n", len(validator.Delegations), endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
message := &msg_pb.Message{
|
|
|
|
ServiceType: msg_pb.ServiceType_CONSENSUS,
|
|
|
|
Type: msg_pb.MessageType_PREPARE,
|
|
|
|
Request: &msg_pb.Message_Consensus{
|
|
|
|
Consensus: &msg_pb.ConsensusRequest{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
blsPriKey := bls.RandPrivateKey()
|
|
|
|
pubKeyWrapper := bls.PublicKeyWrapper{Object: blsPriKey.GetPublicKey()}
|
|
|
|
pubKeyWrapper.Bytes.FromLibBLSPublicKey(pubKeyWrapper.Object)
|
|
|
|
|
|
|
|
request := message.GetConsensus()
|
|
|
|
request.ViewId = 5
|
|
|
|
request.BlockNum = 5
|
|
|
|
request.ShardId = 1
|
|
|
|
// 32 byte block hash
|
|
|
|
request.BlockHash = []byte("stasdlkfjsadlkjfkdsljflksadjf")
|
|
|
|
// sender address
|
|
|
|
request.SenderPubkey = pubKeyWrapper.Bytes[:]
|
|
|
|
|
|
|
|
message.Signature = nil
|
|
|
|
// TODO: use custom serialization method rather than protobuf
|
|
|
|
marshaledMessage, err := protobuf.Marshal(message)
|
|
|
|
// 64 byte of signature on previous data
|
|
|
|
hash1 := hash.Keccak256(marshaledMessage)
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
blsPriKey.SignHash(hash1[:])
|
|
|
|
}
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to sign: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
sig := blsPriKey.SignHash(hash1[:])
|
|
|
|
message.Signature = sig.Serialize()
|
|
|
|
marshaledMessage2, _ := protobuf.Marshal(message)
|
|
|
|
|
|
|
|
message = &msg_pb.Message{}
|
|
|
|
if err := protobuf.Unmarshal(marshaledMessage2, message); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
if err := protobuf.Unmarshal(marshaledMessage2, message); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to unmarshall: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
signature := message.Signature
|
|
|
|
message.Signature = nil
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
protobuf.Marshal(message)
|
|
|
|
}
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to marshal: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
messageBytes, err := protobuf.Marshal(message)
|
|
|
|
msgSig := bls_core.Sign{}
|
|
|
|
err = msgSig.Deserialize(signature)
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
msgSig.Deserialize(signature)
|
|
|
|
}
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to deserialize sig: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
msgHash := hash.Keccak256(messageBytes)
|
|
|
|
if !msgSig.VerifyHash(pubKeyWrapper.Object, msgHash[:]) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
hash.Keccak256(messageBytes)
|
|
|
|
}
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to hash message: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
startTime = time.Now()
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
|
|
msgSig.VerifyHash(pubKeyWrapper.Object, msgHash[:])
|
|
|
|
}
|
|
|
|
endTime = time.Now()
|
|
|
|
fmt.Printf("Time required to verify sig: %f seconds\n", endTime.Sub(startTime).Seconds())
|
|
|
|
|
|
|
|
message.Signature = signature
|
|
|
|
|
|
|
|
// A example result of a single run:
|
|
|
|
//
|
|
|
|
//Time required to calc percentage 100001 delegations: 0.058205 seconds
|
|
|
|
//Time required to reward a validator with 100001 delegations: 0.015543 seconds
|
|
|
|
//Time required to sign: 0.479827 seconds
|
|
|
|
//Time required to unmarshall: 0.000662 seconds
|
|
|
|
//Time required to marshal: 0.000453 seconds
|
|
|
|
//Time required to deserialize sig: 0.517965 seconds
|
|
|
|
//Time required to hash message: 0.001191 seconds
|
|
|
|
//Time required to verify sig: 1.444604 seconds
|
|
|
|
}
|
|
|
|
|
|
|
|
func lookupDelegatorShares(
|
|
|
|
snapshot *staking.ValidatorWrapper,
|
|
|
|
) (result map[common2.Address]numeric.Dec, err error) {
|
|
|
|
result = map[common2.Address]numeric.Dec{}
|
|
|
|
totalDelegationDec := numeric.NewDecFromBigInt(snapshot.TotalDelegation())
|
|
|
|
for i := range snapshot.Delegations {
|
|
|
|
delegation := snapshot.Delegations[i]
|
|
|
|
// NOTE percentage = <this_delegator_amount>/<total_delegation>
|
|
|
|
if totalDelegationDec.IsZero() {
|
|
|
|
utils.Logger().Info().
|
|
|
|
RawJSON("validator-snapshot", []byte(snapshot.String())).
|
|
|
|
Msg("zero total delegation during AddReward delegation payout")
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
percentage := numeric.NewDecFromBigInt(delegation.Amount).Quo(totalDelegationDec)
|
|
|
|
result[delegation.DelegatorAddress] = percentage
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|