The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/staking/slash/double-sign_test.go

1251 lines
32 KiB

package slash
import (
"errors"
"fmt"
"math/big"
"reflect"
"strings"
"testing"
"github.com/harmony-one/harmony/core/rawdb"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/ethereum/go-ethereum/common"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
blockfactory "github.com/harmony-one/harmony/block/factory"
consensus_sig "github.com/harmony-one/harmony/consensus/signature"
"github.com/harmony-one/harmony/core/state"
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/numeric"
"github.com/harmony-one/harmony/shard"
"github.com/harmony-one/harmony/staking/effective"
staking "github.com/harmony-one/harmony/staking/types"
)
var (
bigOne = big.NewInt(1e18)
fiveKOnes = new(big.Int).Mul(big.NewInt(5000), bigOne)
tenKOnes = new(big.Int).Mul(big.NewInt(10000), bigOne)
twentyKOnes = new(big.Int).Mul(big.NewInt(20000), bigOne)
twentyFiveKOnes = new(big.Int).Mul(big.NewInt(25000), bigOne)
thirtyKOnes = new(big.Int).Mul(big.NewInt(30000), bigOne)
thirtyFiveKOnes = new(big.Int).Mul(big.NewInt(35000), bigOne)
fourtyKOnes = new(big.Int).Mul(big.NewInt(40000), bigOne)
fiftyKOnes = new(big.Int).Mul(big.NewInt(50000), bigOne)
hundredKOnes = new(big.Int).Mul(big.NewInt(100000), bigOne)
thousandKOnes = new(big.Int).Mul(big.NewInt(1000000), bigOne)
)
const (
// validator creation parameters
doubleSignShardID = 0
doubleSignEpoch = 4
doubleSignBlockNumber = 37
doubleSignViewID = 38
creationHeight = 33
lastEpochInComm = 5
currentEpoch = 5
)
const (
numShard = 4
numNodePerShard = 5
offenderShard = doubleSignShardID
offenderShardIndex = 0
)
var (
doubleSignBlock1 = makeBlockForTest(doubleSignEpoch, 0)
doubleSignBlock2 = makeBlockForTest(doubleSignEpoch, 1)
)
var (
keyPairs = genKeyPairs(25)
offIndex = offenderShard*numNodePerShard + offenderShardIndex
offAddr = makeTestAddress(offIndex)
offKey = keyPairs[offIndex]
offPub = offKey.Pub()
leaderAddr = makeTestAddress("leader")
reporterAddr = makeTestAddress("reporter")
)
func TestVerify(t *testing.T) {
tests := []struct {
r Record
sdb *state.DB
chain *fakeBlockChain
expErr error
}{
{
r: defaultSlashRecord(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: nil,
},
{
// not vWrapper in state
r: defaultSlashRecord(),
sdb: makeTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errors.New("address not present in state"),
},
{
// banned status
r: defaultSlashRecord(),
sdb: func() *state.DB {
sdb := makeTestStateDB()
w := defaultValidatorWrapper()
w.Status = effective.Banned
if err := sdb.UpdateValidatorWrapper(offAddr, w); err != nil {
panic(err)
}
return sdb
}(),
chain: defaultFakeBlockChain(),
expErr: errAlreadyBannedValidator,
},
{
// same offender and reporter
r: func() Record {
r := defaultSlashRecord()
r.Reporter = offAddr
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errReporterAndOffenderSame,
},
{
// same block in conflicting votes
r: func() Record {
r := defaultSlashRecord()
r.Evidence.SecondVote = copyVote(r.Evidence.FirstVote)
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errSlashBlockNoConflict,
},
{
// second vote signed with a different bls key
r: func() Record {
r := defaultSlashRecord()
r.Evidence.SecondVote = makeVoteData(keyPairs[24], doubleSignBlock2)
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errNoMatchingDoubleSignKeys,
},
{
// block is in the future
r: defaultSlashRecord(),
sdb: defaultTestStateDB(),
chain: func() *fakeBlockChain {
bc := defaultFakeBlockChain()
bc.currentBlock = *makeBlockForTest(doubleSignEpoch-1, 0)
return bc
}(),
expErr: errSlashFromFutureEpoch,
},
{
// error from blockchain.ReadShardState (fakeChainErrEpoch)
r: func() Record {
r := defaultSlashRecord()
r.Evidence.Epoch = big.NewInt(currentEpoch)
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errFakeChainUnexpectEpoch,
},
{
// Invalid shardID
r: func() Record {
r := defaultSlashRecord()
r.Evidence.ShardID = 10
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: shard.ErrShardIDNotInSuperCommittee,
},
{
// Missing bls from committee
r: func() Record {
r := defaultSlashRecord()
r.Evidence.FirstVote = makeVoteData(keyPairs[24], doubleSignBlock1)
r.Evidence.SecondVote = makeVoteData(keyPairs[24], doubleSignBlock2)
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: shard.ErrValidNotInCommittee,
},
{
// offender address not match vote
r: defaultSlashRecord(),
sdb: defaultTestStateDB(),
chain: func() *fakeBlockChain {
bc := defaultFakeBlockChain()
bc.superCommittee.Shards[offenderShard].Slots[offenderShardIndex].
EcdsaAddress = makeTestAddress("other")
return bc
}(),
expErr: errors.New("does not match the signer's address"),
},
{
// empty signature
r: func() Record {
r := defaultSlashRecord()
r.Evidence.FirstVote.Signature = nil
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errors.New("Empty buf"),
},
{
// false signature
r: func() Record {
r := defaultSlashRecord()
r.Evidence.FirstVote.Signature = r.Evidence.SecondVote.Signature
return r
}(),
sdb: defaultTestStateDB(),
chain: defaultFakeBlockChain(),
expErr: errors.New("could not verify bls key signature on slash"),
},
}
for i, test := range tests {
rawRecord := copyRecord(test.r)
err := Verify(test.chain, test.sdb, &test.r)
if assErr := assertError(err, test.expErr); assErr != nil {
t.Errorf("Test %v: %v", i, assErr)
}
if !reflect.DeepEqual(test.r, rawRecord) {
t.Errorf("Test %v: record has value changed", i)
}
}
}
func TestApplySlashRate(t *testing.T) {
tests := []struct {
amount *big.Int
rate numeric.Dec
exp *big.Int
}{
{twentyKOnes, numeric.NewDecWithPrec(5, 1), tenKOnes},
{bigOne, numeric.NewDecWithPrec(5, 1), big.NewInt(5e17)},
{new(big.Int).Mul(bigOne, big.NewInt(3)), numeric.NewDecWithPrec(5, 1), big.NewInt(15e17)},
{twentyKOnes, numeric.ZeroDec(), big.NewInt(0)},
}
for i, test := range tests {
res := applySlashRate(test.amount, test.rate)
if res.Cmp(test.exp) != 0 {
t.Errorf("Test %v: unexpected answer %v / %v", i, res, test.exp)
}
}
}
func TestSetDifference(t *testing.T) {
tests := []struct {
indexes1 []int
indexes2 []int
expIndexes []int
}{
{[]int{}, []int{}, []int{}},
{[]int{1}, []int{}, []int{}},
{[]int{}, []int{1}, []int{1}},
{[]int{1, 2, 3}, []int{3, 4, 5}, []int{4, 5}},
}
for ti, test := range tests {
rs1 := makeSimpleRecords(test.indexes1)
rs2 := makeSimpleRecords(test.indexes2)
diff := rs1.SetDifference(rs2)
if len(diff) != len(test.expIndexes) {
t.Errorf("Test %v: size not expected %v, %v", ti, len(diff), len(test.expIndexes))
}
for i, r := range diff {
if r.Reporter != common.BigToAddress(big.NewInt(int64(test.expIndexes[i]))) {
t.Errorf("Test %v: Records[i] unexpected", ti)
}
}
}
}
func makeSimpleRecords(indexes []int) Records {
rs := make(Records, 0, len(indexes))
for _, index := range indexes {
rs = append(rs, Record{
Reporter: common.BigToAddress(big.NewInt(int64(index))),
})
}
return rs
}
func TestPayDown(t *testing.T) {
tests := []struct {
debt, amt *big.Int
diff *Application
expDebt, expAmt *big.Int
expDiff *Application
expErr error
}{
{
debt: new(big.Int).Set(twentyFiveKOnes),
amt: new(big.Int).Set(thirtyKOnes),
diff: &Application{
TotalSlashed: new(big.Int).Set(tenKOnes),
TotalBeneficiaryReward: new(big.Int).Set(tenKOnes),
},
expDebt: common.Big0,
expAmt: fiveKOnes,
expDiff: &Application{
TotalSlashed: thirtyFiveKOnes,
TotalBeneficiaryReward: tenKOnes,
},
},
{
debt: new(big.Int).Set(thirtyKOnes),
amt: new(big.Int).Set(twentyFiveKOnes),
diff: &Application{
TotalSlashed: new(big.Int).Set(tenKOnes),
TotalBeneficiaryReward: new(big.Int).Set(tenKOnes),
},
expDebt: fiveKOnes,
expAmt: common.Big0,
expDiff: &Application{
TotalSlashed: thirtyFiveKOnes,
TotalBeneficiaryReward: tenKOnes,
},
},
}
for i, test := range tests {
payDown(test.amt, test.debt, test.diff.TotalSlashed)
if test.debt.Cmp(test.expDebt) != 0 {
t.Errorf("Test %v: unexpected debt %v / %v", i, test.debt, test.expDebt)
}
if test.amt.Cmp(test.expAmt) != 0 {
t.Errorf("Test %v: unexpected amount %v / %v", i, test.amt, test.expAmt)
}
if test.diff.TotalSlashed.Cmp(test.expDiff.TotalSlashed) != 0 {
t.Errorf("Test %v: unexpected expSlashed %v / %v", i, test.diff.TotalSlashed,
test.expDiff.TotalSlashed)
}
if test.diff.TotalBeneficiaryReward.Cmp(test.expDiff.TotalBeneficiaryReward) != 0 {
t.Errorf("Test %v: unexpected total beneficiary reward %v / %v", i,
test.diff.TotalBeneficiaryReward, test.expDiff.TotalBeneficiaryReward)
}
}
}
func TestApplySlashingToDelegator(t *testing.T) {
tests := []applySlashingToDelegatorTestCase{
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: big.NewInt(0),
expDel: expDelegation{
expAmt: twentyKOnes,
expReward: tenKOnes,
expUndelAmt: []*big.Int{tenKOnes, tenKOnes},
},
expSlashed: common.Big0,
expBeneficiaryReward: common.Big0,
},
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: twentyKOnes,
expDel: expDelegation{
expAmt: big.NewInt(0),
expReward: tenKOnes,
expUndelAmt: []*big.Int{tenKOnes, tenKOnes},
},
expSlashed: twentyKOnes,
expBeneficiaryReward: tenKOnes,
},
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: twentyFiveKOnes,
expDel: expDelegation{
expAmt: big.NewInt(0),
expReward: tenKOnes,
expUndelAmt: []*big.Int{tenKOnes, fiveKOnes},
},
expSlashed: twentyFiveKOnes,
expBeneficiaryReward: new(big.Int).Div(twentyFiveKOnes, common.Big2),
},
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: thirtyKOnes,
expDel: expDelegation{
expAmt: big.NewInt(0),
expReward: tenKOnes,
expUndelAmt: []*big.Int{tenKOnes, big.NewInt(0)},
},
expSlashed: thirtyKOnes,
expBeneficiaryReward: new(big.Int).Div(thirtyKOnes, common.Big2),
},
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: thirtyFiveKOnes,
expDel: expDelegation{
expAmt: big.NewInt(0),
expReward: fiveKOnes,
expUndelAmt: []*big.Int{tenKOnes, big.NewInt(0)},
},
expSlashed: thirtyFiveKOnes,
expBeneficiaryReward: new(big.Int).Div(thirtyFiveKOnes, common.Big2),
},
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: fourtyKOnes,
expDel: expDelegation{
expAmt: big.NewInt(0),
expReward: big.NewInt(0),
expUndelAmt: []*big.Int{tenKOnes, big.NewInt(0)},
},
expSlashed: fourtyKOnes,
expBeneficiaryReward: twentyKOnes,
},
{
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
delegationIdx: 0,
debt: fiftyKOnes,
expDel: expDelegation{
expAmt: big.NewInt(0),
expReward: big.NewInt(0),
expUndelAmt: []*big.Int{tenKOnes, big.NewInt(0)},
},
expSlashed: fourtyKOnes,
expBeneficiaryReward: twentyKOnes,
},
}
for i, tc := range tests {
tc.makeData()
tc.apply()
if err := tc.checkResult(); err != nil {
t.Errorf("Test %v: %v", i, err)
}
}
}
func TestDelegatorSlashApply(t *testing.T) {
tests := []slashApplyTestCase{
{
snapshot: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: fourtyKOnes,
},
}),
current: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: fourtyKOnes,
},
}),
expDels: []expDelegation{
{
expAmt: twentyKOnes,
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
},
expSlashed: twentyKOnes,
expBeneficiaryReward: tenKOnes,
},
{
snapshot: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: fourtyKOnes,
},
{
address: "del1",
amount: fourtyKOnes,
},
}),
current: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: fourtyKOnes,
},
{
address: "del1",
amount: fourtyKOnes,
},
}),
expDels: []expDelegation{
{
expAmt: twentyKOnes,
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
{
expAmt: new(big.Int).Mul(big.NewInt(24000), bigOne),
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
},
expSlashed: new(big.Int).Mul(big.NewInt(36000), bigOne),
expBeneficiaryReward: new(big.Int).Mul(big.NewInt(18000), bigOne),
},
{
snapshot: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: fiftyKOnes,
},
{
address: "del1",
amount: fourtyKOnes,
},
{
address: "del2",
amount: tenKOnes,
},
}),
current: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: fiftyKOnes,
},
{
address: "del1",
amount: fourtyKOnes,
},
{
address: "del2",
amount: tenKOnes,
},
}),
expDels: []expDelegation{
{
expAmt: twentyFiveKOnes,
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
{
expAmt: new(big.Int).Mul(big.NewInt(24000), bigOne),
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
{
expAmt: new(big.Int).Mul(big.NewInt(6000), bigOne),
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
},
expSlashed: new(big.Int).Mul(big.NewInt(45000), bigOne),
expBeneficiaryReward: new(big.Int).Mul(big.NewInt(22500), bigOne),
},
{
snapshot: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: hundredKOnes,
},
{
address: "del1",
amount: twentyKOnes,
},
{
address: "del2",
amount: twentyKOnes,
},
}),
current: generateValidatorWrapper([]testDelegation{
{
address: "off",
amount: hundredKOnes,
},
{
address: "del1",
amount: common.Big0,
historyUndel: twentyKOnes,
afterSignUndel: common.Big0,
},
{
address: "del2",
amount: common.Big0,
historyUndel: common.Big0,
afterSignUndel: twentyKOnes,
},
}),
expDels: []expDelegation{
{
expAmt: fiftyKOnes,
expReward: tenKOnes,
expUndelAmt: []*big.Int{},
},
{
expAmt: common.Big0,
expReward: common.Big0,
expUndelAmt: []*big.Int{twentyKOnes, common.Big0},
},
{
expAmt: common.Big0,
expReward: tenKOnes,
expUndelAmt: []*big.Int{common.Big0, common.Big0},
},
},
expSlashed: new(big.Int).Mul(big.NewInt(80000), bigOne),
expBeneficiaryReward: fourtyKOnes,
},
}
for i, tc := range tests {
tc.makeData()
tc.apply()
if err := tc.checkResult(); err != nil {
t.Errorf("Test %v: %v", i, err)
}
}
}
type applySlashingToDelegatorTestCase struct {
snapshot, current *staking.ValidatorWrapper
state *state.DB
beneficiary common.Address
slashTrack *Application
debt *big.Int
delegationIdx int
gotErr error
expDel expDelegation
expSlashed, expBeneficiaryReward *big.Int
expErr error
}
type slashApplyTestCase struct {
snapshot, current *staking.ValidatorWrapper
beneficiary common.Address
state *state.DB
slashTrack *Application
gotErr error
expDels []expDelegation
expSlashed, expBeneficiaryReward *big.Int
expErr error
}
func (tc *applySlashingToDelegatorTestCase) makeData() {
tc.beneficiary = leaderAddr
tc.state = makeTestStateDB()
tc.slashTrack = &Application{
TotalSlashed: new(big.Int).Set(common.Big0),
TotalBeneficiaryReward: new(big.Int).Set(common.Big0),
}
}
func (tc *applySlashingToDelegatorTestCase) apply() {
tc.gotErr = delegatorSlashApplyDebt(tc.snapshot, tc.current, tc.state, tc.debt, tc.beneficiary,
big.NewInt(doubleSignEpoch), tc.slashTrack)
}
func (tc *applySlashingToDelegatorTestCase) checkResult() error {
if err := assertError(tc.gotErr, tc.expErr); err != nil {
return err
}
if err := tc.expDel.checkDelegation(tc.current.Delegations[tc.delegationIdx]); err != nil {
return fmt.Errorf("delegations[%v]: %v", tc.delegationIdx, err)
}
if tc.slashTrack.TotalSlashed.Cmp(tc.expSlashed) != 0 {
return fmt.Errorf("unexpected total slash %v / %v", tc.slashTrack.TotalSlashed,
tc.expSlashed)
}
if tc.slashTrack.TotalBeneficiaryReward.Cmp(tc.expBeneficiaryReward) != 0 {
return fmt.Errorf("unexpected beneficiary reward %v / %v", tc.slashTrack.TotalBeneficiaryReward,
tc.expBeneficiaryReward)
}
if bal := tc.state.GetBalance(tc.beneficiary); bal.Cmp(tc.expBeneficiaryReward) != 0 {
return fmt.Errorf("unexpected balance for beneficiary %v / %v", bal, tc.expBeneficiaryReward)
}
return nil
}
func (tc *slashApplyTestCase) makeData() {
tc.beneficiary = leaderAddr
tc.state = makeTestStateDB()
tc.slashTrack = &Application{
TotalSlashed: new(big.Int).Set(common.Big0),
TotalBeneficiaryReward: new(big.Int).Set(common.Big0),
}
}
func (tc *slashApplyTestCase) apply() {
tc.gotErr = delegatorSlashApply(tc.snapshot, tc.current, tc.state, tc.beneficiary, big.NewInt(doubleSignEpoch), tc.slashTrack)
}
func (tc *slashApplyTestCase) checkResult() error {
if err := assertError(tc.gotErr, tc.expErr); err != nil {
return err
}
if len(tc.expDels) != len(tc.current.Delegations) {
return fmt.Errorf("delegations size not expected %v / %v",
len(tc.current.Delegations), len(tc.expDels))
}
for i, expDel := range tc.expDels {
if err := expDel.checkDelegation(tc.current.Delegations[i]); err != nil {
return fmt.Errorf("delegations[%v]: %v", i, err)
}
}
if tc.slashTrack.TotalSlashed.Cmp(tc.expSlashed) != 0 {
return fmt.Errorf("unexpected total slash %v / %v", tc.slashTrack.TotalSlashed,
tc.expSlashed)
}
if tc.slashTrack.TotalBeneficiaryReward.Cmp(tc.expBeneficiaryReward) != 0 {
return fmt.Errorf("unexpected beneficiary reward %v / %v", tc.slashTrack.TotalBeneficiaryReward,
tc.expBeneficiaryReward)
}
if bal := tc.state.GetBalance(tc.beneficiary); bal.Cmp(tc.expBeneficiaryReward) != 0 {
return fmt.Errorf("unexpected balance for beneficiary %v / %v", bal, tc.expBeneficiaryReward)
}
return nil
}
type expDelegation struct {
expAmt, expReward *big.Int
expUndelAmt []*big.Int
}
type testDelegation struct {
address string
amount *big.Int
historyUndel *big.Int
afterSignUndel *big.Int
}
func (ed expDelegation) checkDelegation(d staking.Delegation) error {
if d.Amount.Cmp(ed.expAmt) != 0 {
return fmt.Errorf("unexpected amount %v / %v", d.Amount, ed.expAmt)
}
if d.Reward.Cmp(ed.expReward) != 0 {
return fmt.Errorf("unexpected reward %v / %v", d.Reward, ed.expReward)
}
if len(d.Undelegations) != len(ed.expUndelAmt) {
return fmt.Errorf("unexpected undelegation size %v / %v", len(d.Undelegations),
len(ed.expUndelAmt))
}
for i := range d.Undelegations {
if ed.expUndelAmt[i].Cmp(d.Undelegations[i].Amount) != 0 {
return fmt.Errorf("[%v]th undelegation unexpected amount %v / %v", i,
d.Undelegations[i].Amount, ed.expUndelAmt[i])
}
}
return nil
}
func TestApply(t *testing.T) {
tests := []applyTestCase{
{
// positive test case
snapshot: defaultSnapValidatorWrapper(),
current: defaultCurrentValidatorWrapper(),
slashes: Records{defaultSlashRecord()},
expSlashed: twentyKOnes,
expBeneficiaryReward: tenKOnes,
},
{
// missing snapshot in chain
current: defaultCurrentValidatorWrapper(),
slashes: Records{defaultSlashRecord()},
expErr: errors.New("could not find validator"),
},
{
// missing vWrapper in state
snapshot: defaultSnapValidatorWrapper(),
slashes: Records{defaultSlashRecord()},
expErr: errValidatorNotFoundDuringSlash,
},
}
for i, test := range tests {
test.makeData(t)
test.apply()
if err := test.checkResult(); err != nil {
t.Errorf("Test %v: %v", i, err)
}
}
}
type applyTestCase struct {
snapshot, current *staking.ValidatorWrapper
slashes Records
chain *fakeBlockChain
state, stateSnap *state.DB
gotErr error
gotDiff *Application
expSlashed *big.Int
expBeneficiaryReward *big.Int
expErr error
}
func (tc *applyTestCase) makeData(t *testing.T) {
tc.chain = defaultFakeBlockChain()
if tc.snapshot != nil {
tc.chain.snapshots[tc.snapshot.Address] = *tc.snapshot
}
tc.state = makeTestStateDB()
if tc.current != nil {
if err := tc.state.UpdateValidatorWrapper(tc.current.Address, tc.current); err != nil {
t.Error(err)
}
if _, err := tc.state.Commit(true); err != nil {
t.Error(err)
}
}
tc.stateSnap = tc.state.Copy()
}
func (tc *applyTestCase) apply() {
tc.gotDiff, tc.gotErr = Apply(tc.chain, tc.state, tc.slashes, leaderAddr)
}
func (tc *applyTestCase) checkResult() error {
if err := assertError(tc.gotErr, tc.expErr); err != nil {
return fmt.Errorf("unexpected error %v / %v", tc.gotErr, tc.expErr)
}
if (tc.gotErr != nil) || (tc.expErr != nil) {
return nil
}
if tc.gotDiff.TotalSlashed.Cmp(tc.expSlashed) != 0 {
return fmt.Errorf("unexpected total slash %v / %v", tc.gotDiff.TotalSlashed,
tc.expSlashed)
}
if tc.gotDiff.TotalBeneficiaryReward.Cmp(tc.expBeneficiaryReward) != 0 {
return fmt.Errorf("unexpected beneficiary reward %v / %v", tc.gotDiff.TotalBeneficiaryReward,
tc.expBeneficiaryReward)
}
if err := tc.checkState(); err != nil {
return fmt.Errorf("state check: %v", err)
}
return nil
}
// checkState checks whether the state has been banned and whether the delegations
// are different from the original
func (tc *applyTestCase) checkState() error {
vw, err := tc.state.ValidatorWrapper(offAddr, true, false)
if err != nil {
return err
}
if !IsBanned(vw) {
return fmt.Errorf("status not banned")
}
vwSnap, err := tc.stateSnap.ValidatorWrapper(offAddr, true, false)
if err != nil {
return err
}
if reflect.DeepEqual(vwSnap.Delegations, vw.Delegations) {
return fmt.Errorf("status still unchanged")
}
return nil
}
func defaultSlashRecord() Record {
return Record{
Evidence: Evidence{
ConflictingVotes: ConflictingVotes{
FirstVote: makeVoteData(offKey, doubleSignBlock1),
SecondVote: makeVoteData(offKey, doubleSignBlock2),
},
Moment: Moment{
Epoch: big.NewInt(doubleSignEpoch),
ShardID: doubleSignShardID,
Height: doubleSignBlockNumber,
ViewID: doubleSignViewID,
},
Offender: offAddr,
},
Reporter: reporterAddr,
}
}
func makeVoteData(kp blsKeyPair, block *types.Block) Vote {
return Vote{
SignerPubKeys: []bls.SerializedPublicKey{kp.Pub()},
BlockHeaderHash: block.Hash(),
Signature: kp.Sign(block),
}
}
func makeTestAddress(item interface{}) common.Address {
s := fmt.Sprintf("harmony.one.%v", item)
return common.BytesToAddress([]byte(s))
}
func makeBlockForTest(epoch int64, index int) *types.Block {
h := blockfactory.NewTestHeader()
h.SetEpoch(big.NewInt(epoch))
h.SetNumber(big.NewInt(doubleSignBlockNumber))
h.SetViewID(big.NewInt(doubleSignViewID))
h.SetRoot(common.BigToHash(big.NewInt(int64(index))))
return types.NewBlockWithHeader(h)
}
func defaultValidatorWrapper() *staking.ValidatorWrapper {
pubKeys := []bls.SerializedPublicKey{offPub}
v := defaultTestValidator(pubKeys)
ds := defaultTestDelegations()
return &staking.ValidatorWrapper{
Validator: v,
Delegations: ds,
}
}
func defaultSnapValidatorWrapper() *staking.ValidatorWrapper {
return defaultValidatorWrapper()
}
func defaultCurrentValidatorWrapper() *staking.ValidatorWrapper {
pubKeys := []bls.SerializedPublicKey{offPub}
v := defaultTestValidator(pubKeys)
ds := defaultDelegationsWithUndelegates()
return &staking.ValidatorWrapper{
Validator: v,
Delegations: ds,
}
}
// defaultTestValidator makes a valid Validator kps structure
func defaultTestValidator(pubKeys []bls.SerializedPublicKey) staking.Validator {
comm := staking.Commission{
CommissionRates: staking.CommissionRates{
Rate: numeric.MustNewDecFromStr("0.167983520183826780"),
MaxRate: numeric.MustNewDecFromStr("0.179184469782137200"),
MaxChangeRate: numeric.MustNewDecFromStr("0.152212761523253600"),
},
UpdateHeight: big.NewInt(10),
}
desc := staking.Description{
Name: "someoneA",
Identity: "someoneB",
Website: "someoneC",
SecurityContact: "someoneD",
Details: "someoneE",
}
return staking.Validator{
Address: offAddr,
SlotPubKeys: pubKeys,
LastEpochInCommittee: big.NewInt(lastEpochInComm),
MinSelfDelegation: new(big.Int).Set(tenKOnes),
MaxTotalDelegation: new(big.Int).Set(thousandKOnes),
Status: effective.Active,
Commission: comm,
Description: desc,
CreationHeight: big.NewInt(creationHeight),
}
}
func generateValidatorWrapper(delData []testDelegation) *staking.ValidatorWrapper {
pubKeys := []bls.SerializedPublicKey{offPub}
v := defaultTestValidator(pubKeys)
ds := generateDelegations(delData)
return &staking.ValidatorWrapper{
Validator: v,
Delegations: ds,
}
}
func generateDelegations(delData []testDelegation) staking.Delegations {
delegations := make(staking.Delegations, len(delData))
for i, del := range delData {
delegations[i] = makeDelegation(makeTestAddress(del.address), new(big.Int).Set(del.amount))
if del.historyUndel != nil {
delegations[i].Undelegations = append(
delegations[i].Undelegations,
staking.Undelegation{
Amount: new(big.Int).Set(del.historyUndel),
Epoch: big.NewInt(doubleSignEpoch - 1),
},
)
}
if del.afterSignUndel != nil {
delegations[i].Undelegations = append(
delegations[i].Undelegations,
staking.Undelegation{
Amount: new(big.Int).Set(del.afterSignUndel),
Epoch: big.NewInt(doubleSignEpoch + 1),
},
)
}
}
return delegations
}
func defaultTestDelegations() staking.Delegations {
return staking.Delegations{
makeDelegation(offAddr, new(big.Int).Set(fourtyKOnes)),
makeDelegation(makeTestAddress("del1"), new(big.Int).Set(fourtyKOnes)),
}
}
func defaultDelegationsWithUndelegates() staking.Delegations {
d1 := makeDelegation(offAddr, new(big.Int).Set(twentyKOnes))
d1.Undelegations = []staking.Undelegation{
makeHistoryUndelegation(),
makeDefaultUndelegation(),
}
d2 := makeDelegation(makeTestAddress("del2"), new(big.Int).Set(fourtyKOnes))
return staking.Delegations{d1, d2}
}
func makeDelegation(addr common.Address, amount *big.Int) staking.Delegation {
return staking.Delegation{
DelegatorAddress: addr,
Amount: amount,
Reward: new(big.Int).Set(tenKOnes),
}
}
func makeDefaultUndelegation() staking.Undelegation {
return staking.Undelegation{
Amount: new(big.Int).Set(tenKOnes),
Epoch: big.NewInt(doubleSignEpoch + 2),
}
}
func makeHistoryUndelegation() staking.Undelegation {
return staking.Undelegation{
Amount: new(big.Int).Set(tenKOnes),
Epoch: big.NewInt(doubleSignEpoch - 1),
}
}
// makeCommitteeFromKeyPairs makes a shard state for testing.
// address is generated by makeTestAddress
// bls key is get from the variable keyPairs []blsKeyPair
func makeDefaultCommittee() shard.State {
epoch := big.NewInt(doubleSignEpoch)
maker := newShardSlotMaker(keyPairs)
return makeCommitteeBySlotMaker(epoch, maker)
}
func makeCommitteeBySlotMaker(epoch *big.Int, maker shardSlotMaker) shard.State {
sstate := shard.State{
Epoch: epoch,
Shards: make([]shard.Committee, 0, int(numShard)),
}
for sid := uint32(0); sid != numNodePerShard; sid++ {
sstate.Shards = append(sstate.Shards, makeShardBySlotMaker(sid, maker))
}
return sstate
}
func makeShardBySlotMaker(shardID uint32, maker shardSlotMaker) shard.Committee {
cmt := shard.Committee{
ShardID: shardID,
Slots: make(shard.SlotList, 0, numNodePerShard),
}
for nid := 0; nid != numNodePerShard; nid++ {
cmt.Slots = append(cmt.Slots, maker.makeSlot())
}
return cmt
}
type shardSlotMaker struct {
kps []blsKeyPair
i int
}
func newShardSlotMaker(kps []blsKeyPair) shardSlotMaker {
return shardSlotMaker{kps, 0}
}
func (maker *shardSlotMaker) makeSlot() shard.Slot {
s := shard.Slot{
EcdsaAddress: makeTestAddress(maker.i),
BLSPublicKey: maker.kps[maker.i].Pub(), // Yes, will panic when not enough kps
}
maker.i++
return s
}
type blsKeyPair struct {
pri *bls_core.SecretKey
pub *bls_core.PublicKey
}
func genKeyPairs(size int) []blsKeyPair {
kps := make([]blsKeyPair, 0, size)
for i := 0; i != size; i++ {
kps = append(kps, genKeyPair())
}
return kps
}
func genKeyPair() blsKeyPair {
pri := bls.RandPrivateKey()
pub := pri.GetPublicKey()
return blsKeyPair{
pri: pri,
pub: pub,
}
}
func (kp blsKeyPair) Pub() bls.SerializedPublicKey {
var pub bls.SerializedPublicKey
copy(pub[:], kp.pub.Serialize())
return pub
}
func (kp blsKeyPair) Sign(block *types.Block) []byte {
msg := consensus_sig.ConstructCommitPayload(params.LocalnetChainConfig, block.Epoch(), block.Hash(),
block.Number().Uint64(), block.Header().ViewID().Uint64())
sig := kp.pri.SignHash(msg)
return sig.Serialize()
}
func defaultTestStateDB() *state.DB {
sdb := makeTestStateDB()
err := sdb.UpdateValidatorWrapper(offAddr, defaultCurrentValidatorWrapper())
if err != nil {
panic(err)
}
return sdb
}
func makeTestStateDB() *state.DB {
db := state.NewDatabase(rawdb.NewMemoryDatabase())
sdb, err := state.New(common.Hash{}, db, nil)
if err != nil {
panic(err)
}
return sdb
}
func defaultFakeBlockChain() *fakeBlockChain {
return &fakeBlockChain{
config: *params.LocalnetChainConfig,
currentBlock: *makeBlockForTest(currentEpoch, 0),
superCommittee: makeDefaultCommittee(),
snapshots: make(map[common.Address]staking.ValidatorWrapper),
}
}
func assertError(got, exp error) error {
if (got == nil) != (exp == nil) {
return fmt.Errorf("unexpected error [%v] / [%v]", got, exp)
}
if got == nil {
return nil
}
if !strings.Contains(got.Error(), exp.Error()) {
return fmt.Errorf("unexpected error [%v] / [%v]", got, exp)
}
return nil
}
// copyRecord makes a deep copy the slash Record
func copyRecord(r Record) Record {
return Record{
Evidence: copyEvidence(r.Evidence),
Reporter: r.Reporter,
}
}
// copyEvidence makes a deep copy the slash evidence
func copyEvidence(e Evidence) Evidence {
return Evidence{
Moment: copyMoment(e.Moment),
ConflictingVotes: copyConflictingVotes(e.ConflictingVotes),
Offender: e.Offender,
}
}
// copyMoment makes a deep copy of the Moment structure
func copyMoment(m Moment) Moment {
cp := Moment{
ShardID: m.ShardID,
Height: m.Height,
ViewID: m.ViewID,
}
if m.Epoch != nil {
cp.Epoch = new(big.Int).Set(m.Epoch)
}
return cp
}
// copyConflictingVotes makes a deep copy of slash.ConflictingVotes
func copyConflictingVotes(cv ConflictingVotes) ConflictingVotes {
return ConflictingVotes{
FirstVote: copyVote(cv.FirstVote),
SecondVote: copyVote(cv.SecondVote),
}
}
// copyVote makes a deep copy of slash.Vote
func copyVote(v Vote) Vote {
cp := Vote{
SignerPubKeys: v.SignerPubKeys,
BlockHeaderHash: v.BlockHeaderHash,
}
if v.Signature != nil {
cp.Signature = make([]byte, len(v.Signature))
copy(cp.Signature, v.Signature)
}
return cp
}