Don't Let Delegators Act Up!

Well butter my biscuit and call me Joe! Is it already that time again, amigos? Unless someone presents evidence to the contrary, it would seem it is! Things move so fast these days, and here at Bearmint, development never really slows down! That’s right friends, we’re hard at work makin’ sure everythin’ is right as rain before we officially launch, and that takes time and, most of all, a good deal of patience. While we’re eager to complete our work and move on to the next phase, things have to be done right so that everythin’ goes real nice and smooth…

So while we continue to spit shine everythin’, we’ll be releasin’ some more guides so that you can prepare yerself fer when the time arises. Now last time we talked about buildin’ and implementin’ a slashin’ mechanism fer validators, so today we’re gonna talk about much the same thing, except this time the slashin’ mechanism is fer delegators.

What is a delegator, you ask? Simply put, it’s a wallet that votes fer validators to secure the network. In this regard, delegators play a crucial role in ensuring that the network is kept safe from harm and that only the best validators are chosen to carry out their duty. Of course, not all delegators make great decisions, and they may choose a validator because they’re promised huge payouts or somethin’ akin to that. They may also vote fer a validator fer selfish reasons or any number of things that may be entirely dishonest or nefarious in nature.

Whatever the case, as there are incompetent or malicious validators, so too are there delegators that don’t do their due diligence or actively work to sabotage the network (Lord knows why but it happens). As a result, you may also want (or even need) to punish bad delegators by introducin’ a slashin’ mechanism to yer own application. Now this shouldn’t be taken lightly and may not apply to your unique use case, but if it does, we’ll show you exactly how to build and implement a slashin’ mechanism fer delegators.

So as usual, before we get into the guide, we ask that you take a look at the followin’ terms and definitions so you know exactly what it is we’re talkin’ about when these items show up in the code! Here you go!

Let’s Go Over That One More Time, Friend…

  • @bearmint/bep3 is an NPM package that offers an implementation of BEP-003 which defines a standardized set of assertions that can verify whether certain expectations were fulfilled or not
  • @bearmint/bep13 is an NPM package that offers an implementation of BEP-013 which allows for the sharing of a set of types between all the modules that exist within Bearmint
  • @bearmint/bep16 is an NPM package that offers an implementation of BEP-016 which contains a standardized method of bootstrapping the plugins and application via service providers
  • @bearmint/bep19 is an NPM package that offers an implementation of BEP-019 which explains how protocol buffers will apply in order to provide a standard method of serialization and deserialization
  • @bearmint/bep101 is an NPM package that offers an implementation of BEP-101 which describes an approach to punishing accounts for misbehaving (also referred to as ‘slashing’), and
  • @bearmint/bep108 refers to an NPM package that offers an implementation of BEP-108 which defines the application of Protocol Buffers within Tendermint

Punishing a Delegator

Whenever Tendermint tells us about an event that qualifies as a misbehavior, it’s important to evaluate the misbehavior in question so that the appropriate punishment can be handed out. Due to this fact, several rudimentary checks exist by default.

Checking if the Misbehavior Exists

If the type of misbehavior is neither a number nor known by Tendermint, we’ll need to avoid imposing any kind of punishment since we don’t have any real means of deciding what kind of penalty should be imposed.

if (typeof type !== 'number') {
continue;
}
if (!Object.values(tendermint.abci.EvidenceType).includes(type)) {
continue;
}

Carrying Out the Required Slashing

If the misbehavior is known, we’ll slash an account according to the severity of the misbehavior in question. Note that these punishments take the form of percentage-based reductions in stake, so be very careful when configuring them.

const validatorAccount = await state
.getAccounts()
.findByValidatorAddress(arrayBufferToHex(validator.address));
const slashedStakes: AccountStakes = {};
for (const delegatorAddress of Object.keys(validatorAccount.validator.delegators)) {
const delegator = await state.getAccounts().findByAddress(delegatorAddress);
slashedStakes[delegator.address] = {
...slashedStakes[delegator.address],
amount: await slashAccount({
account: delegator,
punishments: getModuleMilestone<SlasherConfiguration>({
handler: '@bearmint/bep103',
state,
}).punishments,
state,
type,
validator: validatorAccount.validator.name,
}),
};
}
await mergeSlashedStakes({ address: validator.address, stakes: slashedStakes, state });

Combining the Various Elements

In the second-to-last step, we combine everything to create a slashing mechanism that allows us to impose punishments for various misbehaviors.

import { assert } from '@bearmint/bep3';
import { AccountStakes, Slasher, SlasherConfiguration, StateStore } from '@bearmint/bep13';
import { arrayBufferToHex } from '@bearmint/bep19';
import { mergeSlashedStakes, slashAccount } from '@bearmint/bep101';
import { tendermint } from '@bearmint/bep108';
function makeDelegatorSlasher(): Slasher {
return {
async execute({
misbehaviors,
state,
}: {
misbehaviors: tendermint.abci.IEvidence[];
state: StateStore;
}) {
for (const { type, validator } of misbehaviors) {
if (typeof type !== 'number') {
continue;
}
if (!Object.values(tendermint.abci.EvidenceType).includes(type)) {
continue;
}
assert.defined<Uint8Array>(validator?.address);
const validatorAccount = await state
.getAccounts()
.findByValidatorAddress(arrayBufferToHex(validator.address));
const slashedStakes: AccountStakes = {};
for (const delegatorAddress of Object.keys(validatorAccount.validator.delegators)) {
const delegator = await state.getAccounts().findByAddress(delegatorAddress);
slashedStakes[delegator.address] = {
...slashedStakes[delegator.address],
amount: await slashAccount({
account: delegator,
punishments: getModuleMilestone<SlasherConfiguration>({
handler: '@bearmint/bep103',
state,
}).punishments,
state,
type,
validator: validatorAccount.validator.name,
}),
};
}
await mergeSlashedStakes({ address: validator.address, stakes: slashedStakes, state });
}
},
};
}

Registering With the Application

And finally, we simply bind our newly-created function to the container as ContainerType.DelegatorSlasher - Bearmint will now use our slasher whenever it needs to punish a delegator.

import { ContainerType, Cradle, ServiceProvider } from '@bearmint/bep13';
import { makeServiceProviderSkeleton } from '@bearmint/bep16';
import { z } from 'zod';
import { makeDelegatorSlasher } from './slasher';
function makeServiceProvider(cradle: Cradle): ServiceProvider {
const base = makeServiceProviderSkeleton(__dirname);
return {
...base,
configSchema() {
return z.object({
punishments: z.object({
duplicateVote: z.number(),
lightClientAttack: z.number(),
unknown: z.number(),
}),
});
},
async register() {
cradle.Container.bind(ContainerType.DelegatorSlasher).toFunctionSingleton(
makeDelegatorSlasher,
);
},
};
}

It’s All Over and Yer Done, Partners!

That really wasn’t too difficult now, was it? Yep, the setup process fer a slashin’ mechanism fer a delegator ain’t all that complex or time-consumin’, so if you know what yer doin’, this shouldn’t be much of a challenge fer you. Havin’ said this, if you do get stuck or need help, please get in touch with us so we can assist you accordingly.

We don’t expect you to sit there and struggle all by yer lonesome, so if you feel like pullin’ yer hair out or steam’s comin’ out both of yer ears, then fret not as we’re available to help and ease yer sufferin, friend! All of us here in the Bearmint gang are committed to doin’ our very best to help our end users, so just holler at us if ever you need assistance!