Custodian vote mechanics: gaming the system?

Hi there,

(Doomsday theories back again – sorry :slightly_smiling:)

Going off of https://nubits.com/nushares/voting-mechanics#how-does-a-custodian-vote-work and the NuBits whitepaper, I’m hoping to bounce this idea off someone… My preferred answer would be “Nope! Can’t do that! Here’s why .”

There are two documented rules for passing a custodian vote:

  1. 5,001 out of the last 10,000 blocks must vote for the grant
  2. Those 5,001 blocks have to hold at least 50% of the share-age in that same 10,000 block window

This leads me to two questions:

  1. What would it take to for a single person (not necessarily a single address) to be able to mint 5,001 blocks in a 10,000 block window?
  2. What would it take for that same person to be able to hold 50% of the share age in the 10,000 block window?

Some thoughts…

Really really old shares?

If blocks were chosen based on their share age without a cap, holding them for a long time would increase the chances that they would be chosen. Looks like we have a fixed age that’s considered when choosing which block gets to mint: 7 days minimum age considered, and 7 days maximum age considered.

In other words, holding onto shares for a long time won’t increase the chances that your shares will be used to mint the next block.

In code:

static const int STAKE_MIN_AGE = 60 * 60 * 24 * 7; // changed to 7 days so only one vote in 10000 block voting period can be made
static const int STAKE_MAX_AGE = STAKE_MIN_AGE + 1; // changed to same as minimum to incentivize minting as soon as possible

This also seems to hint that the age requirement isn’t as important – as the min and max age considered is 7 days on the nose.

Buying lots of shares

This is the obvious one. If you have a large proportion of the shares issued (say 51+%), there’s a decent chance that you could get 5k+ of the last 10k blocks.
As we’ve talked about elsewhere, this is totally possible, but the hope is that anyone who owns such a large proportion of the networks shares would care enough about their investment not to tank it.

Luck…

How many shares does someone need to “get lucky”? What are the odds given N addresses each with 10k NSR?

I think we’re looking at… (assuming the blocks total are ~82k, and each block is 10k shares, and someone owns N 10k blocks)

Binomial[5k+1, 10k] * (n/82k)^5k+1 * ((82k-n)/82k)^4999

I can’t seem to get Wolfram Alpha to graph me a nice picture for this though… (anyone ?)

It seems like, assuming blocks are purely randomly selected, the chances of winning exactly (not at least) 5,001 blocks is pretty low even as you get towards a third of the shares outstanding.

Bigger share chunks?

The last idea is one I’m not quite sure about, and need someone a bit more versed in the code to tell me: Do we prioritize blocks with larger numbers of shares offered to mint a block?

It looks like you can offer up more than 10k shares (that’s just the minimum), but do two similarly aged chunks of shares get randomly chosen? Or does the larger one (containing more share-days) get priority to mint the block?

If larger chunks get priority, then it stands to reason that someone could turn their shares into 5,001 * 15k chunks, and get priority to mint lots of blocks in a row (because everyone else on the network is using blocks much closer to 10k). This would require 75 million NSR, which is in the 10% range of ownership. Not at all a small investment, but certainly far lower than the 51% discussed before.

Does anyone know where the code is in the Nu client that decides which of two different chains should be built on? Does that logic use share-days in determining the priority?

3 Likes

I don’t think it has been implemented yet, but a change to this has already been planned for that gives all shares the same voting weight.

@sigmike is probably the most qualified to answer a lot of these questions if he has time, but some others may have some insights as well.

Gotcha, so it sounds like… as of right now, we’re looking at a 10% attack…

Anyone able to offer up slightly larger share-chunks (or older share chunks) will be weighted more heavily, and therefore be able to focus their 10% ownership into a single 10k block section, passing motions they wouldn’t be able to in a more even distribution, yes?

2 Likes

The optimal output size for minting blocks is 10,000, and the client automatically splits outputs into 10k chunks over time to maximize minting potential of a users share supply. Outputs used to mint a block become locked for seven days, so they cannot be used again during that period. Those in combination make it very difficult for someone to brute force a succession of blocks. As your supply becomes locked up your influence would diminish significantly before the vote would reach a passing mark and increasing your chunk size is less optimal than splitting all your shares into 10k outputs.

There’s a number of smaller anecdotal discussions that reference this. I’m a bit tied up at the moment but I can go back and find some old issues and discussions about this later.

The real attack involves letting all your shares build up the 7 days, then minting with all at once (i.e. abstaining from minting when you aren’t voting on a grant). Still, I don’t think you’re correct about the 10%. The number should be higher, like 45% required to get lucky.

(I did a simulation for this when messing with freq voting: https://github.com/Nagalim/FrequencyVotingSimulation)

I thought the maximum considered was 7 days for exactly this reason, no?

The comment in the code was …

// changed to same as minimum to incentivize minting as soon as possible

Maybe I wasn’t super clear. I agree with you that “to get lucky” you’d need a pretty high proportion of shares (40+% sounds right to me)

The argument that got me to the 10% number was that if the way to choose a block is based on share days destroyed (with a maximum number of days = 7), then the “priority” becomes (pseudocode):

MIN_STAKE = 10000
MIN_AGE = 60*60*24*7  # 7 days
MAX_AGE = MIN_AGE + 1  # 7 days + 1 second

def get_share_age(shares):
  if (shares.age < MIN_AGE):
    raise "Shares not old enough!"
  return min(MAX_AGE, shares.age)

def get_share_count(shares):
  if (shares.count < MIN_STAKE):
    raise "Not enough shares to stake!"
  return shares.count

def get_priority(shares):
  return get_share_count(shares) * get_share_age(shares)

Since get_share_age can return at most 7 days, this would mean the only knob we have to turn (aka, the thing we can use to cheat) is the number of shares offered up to mint a single block (get_share_age). In other words, this logic says “more shares volunteered == more likelihood of your block being chosen”.

Then there’s the fact that we’ve made it advantageous for people to break up their shares into chunks of MIN_STAKE (since the reward is per block, the incentive is to mint as many blocks as possible). This means that most blocks volunteered would be in the ~10k range (I know most of mine are), because large chunk sizes are not optimal to earning the highest returns.

But what if your goal isn’t to earn the most returns?

What if you’re interested, instead, in getting the most blocks staked in a single window for the purpose of, let’s say, voting to grant yourself some amount of NBT…? Wouldn’t it suddenly be advantageous to offer up more shares than everyone else currently is (just above the average of the last few thousand blocks) ? If you have a higher priority, you get more blocks minted, right?

Assuming the average is in the ballpark of 12k NSR, you could offer up 15k per chunk, and get your blocks prioritized over everyone else’s, right?

If that’s true, then you’d need somewhere around 75m shares (15k * 5000), which is closer to 10% of the 820m outstanding…

It’s still a lot – but it isn’t quite the same as the 51% attack…

1 Like

I dont get it, if you have a higher chance of minting blocks then you have a higher reward and everyone would do it. The reward is fixed per block.

So the absolute minimum amount you can have is 50% * 10,000NSR * 10,000blocks = 50 million NSR. That assumes that all of your NSR mints within the 1 week window (which is ~10,000 blocks). Already 50 million NSR is >15% of the voting shares (~300 million). Next you take into account that a single output can sometimes take weeks (or a month) to mint and unless you’re able to seriously spike your priority (which I don’t think a slight change in staked shares will do, I have several outputs slightly above 10k and I haven’t seen them staking super often or anything) you will need probably >25% or more, I would guess probably more like 35% or 40%, and that’s including luck.

The priority wouldn’t be used like if priority1 > priority2 then 1 gets to mint (at least I don’t think so). It would be like mint chance = baseline * priority and going from 10k to 15k would increase your likelihood of minting with that output by 50%.

Maybe a concrete example would help here.

Let’s say the network has 4 people, A, B, C, and D, and they have 1 million shares each. Let’s also say that all that’s needed to get a vote is 50 blocks in a 100-block window.

Today, they each volunteer 10k chunks, and statistically should each get 25 of the next 100 blocks accepted. It’s not as likely that they’d get 50 blocks straight though…

If the priority is determined by the number of shares, then A using 20k shares per block would have double the priority of B, C, and Ds blocks, right? If so, the the next 100 blocks would (I assume) look like…

  • 50 of A’s blocks (because P=20k*7)
  • 50 of B, C, and D’s blocks randomly selected (A is out of blocks, and they have P=10k*7)

This means…

  1. A earns half as much as is possible (after 50 blocks minted, all shares are now staked, vs 100 blocks possible from the total holdings)
  2. A got enough blocks to pass a vote (50 / 100)
  3. B, C, and D should each get 1/3 of the next blocks, since A doesn’t have any unstaked.

Bringing this back to real-world stuff…

I’d love to see some code… My understanding of this is that it’s not a centralized “block chooser” but instead a distributed consensus mechanism.

More concretely, it would mean that each client is saying “here’s the chain with my new block!” and sending that around to peers.

For that version of the chain to be “accepted”, everyone else who’s capable of minting has to go “OK yea, your block is best. SGTM. I’ll re-broadcast this to my friends too.”

Sometimes, they don’t say that, and instead say “Like hell! My block here is higher priority. I’m not telling anybody about your block. I’m sending my version to everyone!”

And then everyone else on the network goes “Hm, yea, this one is higher priority… Sorry dude, he’s right.”

Sometimes they say “Hm, yea yours was higher priority, but someone else has already built on that block, so the combined priority is the kicker… We’re going with the longer chain. Sorry!”

At least that’s my understanding based on my knowledge of Bitcoin (though I realize that this is … not Bitcoin).

Either way, the truth is in the code, so I’m hoping someone can point me towards that in the BitBucket repository…? I’m sure there are ways to randomize the selection, but didn’t know that was in effect today… Do we have a doc somewhere saying “Here’s how the network selects the next block based on all the potential options” ?

So you’re only concerned with the case where two versions of the same block are found within the few second propagation time? Then priority isn’t even considered in the vast majority of cases, so increasing the stake would have minimal effect (maybe 5% increased block chance for using 50% more stake?)

A would not be able to mint every one of their outputs. As their number of unstaked outputs decreases B,C, and D would have increased chances to stake (difficulty going down) and A would end up with <50%.

Can you point me to some code for that? I’m a bit confused still on how exactly the “chances” come into play, since there is no central dice-roller deciding between which block to choose, rather each individual needs a way to saying “yep, that block is good”. Right?

https://bitbucket.org/JordanLeePeershares/nubit/src/cd6e9ee263579fc3e2cddd3cad1e6eef685112a5/src/main.cpp?at=master

Try starting with line 2120. Basically, everybody’s putting out blocks with a certain stochastic probability. If two people find a block at the same time, that’s called a ‘fork’, and this is how that situation gets resolved.

What I’m saying is that you should think of your shares as having something like a half-life in particle physics. If a chunk of radioactive material is very small, while the half-life is the same as a big chunk, the chance of an individual event goes down. You cannot assume that that material will output a constant amount of radiation. In the same way, we cannot expect your pool of unstaked NSR to mint at a constant rate because it is using itself up.

Interesting to observe that Nu has probably its specific xx% attack, with xx to determine.
Also, with the minting PoS diff going down, time is ripe for shareholders to re-discuss the network’s security.

1 Like

Maybe I’m missing something big then. I was under the impression that if you had a single address with 50,000 NSR, it acts the same as 5 addresses with 10,000 NSR in each.

Is that mistaken?

My argument about “chunks offered up” is saying that a chunk of 10,320 NSR (example) would always be accepted before a chunk of 10,000 even (example).

That said, using those two transactions as examples, it looks like they had pretty different SDD numbers, meaning perhaps the limit isn’t really limited to a cap of 7 days…?

The 10,000 chunk had 157,608.44907 SDD (15.760844907 / share)
The 10,320 chunk had 121,648.07500 SDD (11.787604167 / share)

You are mistaken. Outputs split into 10k chunks are much more optimal for minting a block than large chunks. Which is why the client automatically over time splits a users share supply into 10k chunks to maximize minting potential.

Once again I can’t point to the code right now, and I can’t find the previous discussions (other than the anecdotal discussion I referenced in my post above). I’ll try to find some more. Maybe when @sigmike gets a chance he can come by and clear up some of your questions.

1 Like

Err maybe I wasn’t clear.

Option 1: Single address with a balance of 50k NSR. This would split into 10k chunks, and be capable of minting a total of 5*40 = 200 NSR per “cycle”

Option 2: 5 addresses, each with 10k balances. Same as above, yes?

This is incorrect. The 10,320 has a slightly higher chance of minting at any given time than the 10,000 chunk, but at any point in time either could mint before the other.

If you have 5 outputs, let’s say you have a 0.05% chance of minting a given block. Then, you mint one, so now you have 4 outputs and therefore a 0.04% chance to mint (assuming other minters have a constant amount of unstaked txns). Getting every single output to stake within a 10,000 block window is highly unlikely for anything more than a few outputs.

I think this is the fundamental piece where I’m hung up.

What is the randomness function here? It certainly isn’t “Everyone sends their blocks to a server and the server randomly blesses one of the blocks as chosen.”

In Bitcoin, it’s always “the longest chain wins”, which roughly translates to "whoever first found a nonce such that H(block+nonce) < target". In the case of a network partition (part A hears about block A, part B hears about block B all around the same time), the decision of which becomes “accepted” is kicked down the road (ie, if B’s chain finds a block before A’s, the group working on A will realize their chain is shorter, and jump ship to the other side, and block A goes away).

Where I’m confused is… where’s the random selection while maintaining consensus with proof-of-stake? In Bitcoin it’s “longest-chain wins”, and if there are two equal length chains, it’s “the first I heard about until I see something longer”. Is there a paper somewhere explaining how we deal with this?

2 Likes

So now im going off my beaten path cause ove never read the codebase. However, here’s how i understand it:

When your output gets created, it has a hash or whatever. There is a perfect time for that hash to be used to form a proper block, and that time is actually known when you create the output (i.e. you can in fact calculate when your next mint will be with each output). So there are two forms of ‘randomness’, the predictable pseudo-random stochastic selection of minting appointments given by the complexitity of the minting algorithm, and the other randomness which comes from human beings missing their minting windows in an unpredictable fashion.

Alas, I’m no programmer, but these might be parts of the source code to start with the search for an answer to the question how to determine the “right” chain in case of a fork:
https://bitbucket.org/JordanLeePeershares/nubit/src/cd6e9ee263579fc3e2cddd3cad1e6eef685112a5/src/main.h?at=master#main.h-2124
https://bitbucket.org/JordanLeePeershares/nubit/src/cd6e9ee263579fc3e2cddd3cad1e6eef685112a5/src/main.cpp?at=master#main.cpp-1442

As Nu has inherited Peercoin’s consensus mechanism, this might help as well: https://wiki.peercointalk.org/index.php?title=Peercoin_chain_trust

1 Like

You need to study the fundamentals of Peercoin’s POS. A good place to start, besides reading, is asking specific questions in http://www.peercointalk.org