Consensus and Miner Incentives
See original GitHub issueStumbled across this repo the other day and I absolutely love it! I just have a few of questions about miner incentives and how they relate to transaction evaluation. I’m asking these questions because I think the project is cool and would like to understand it better. Thanks for taking the time to take a look at them!
Transaction Evaluation Incentive
If the transaction is an eval() block, then an user can just include a payment to the miner on it.
First question: How can a miner know the reward they earn associated with a transaction before evaluating it? For instance, say I have this block:
eval Bob(signature) {
CatCoin.send(@Alice, 1000)
Work.doComputationallyExpensiveThing(1000000)
CatCoin.send($block_miner(), 10000)
}
How can the miner verify that they get a massive reward by including this transaction in a block, without having to do the work before evaluation?
The evaluator may not be able to rely on the fact that the line CatCoin.send($block_miner(), 10000)
is included.
What if Bob
is out of money after sending to Alice
? Even if Bob
still has money, Work.doComputationallyExpensiveThing
could send more money to others. If a sub-transaction fails, how can the user guarantee that the rest of the bond also fails? (Or are bonds not atomic in this sense; would this have to be implemented manually?)
The miner could technically look at the set of bonds each function touches to determine which expressions don’t intersect and can be evaluated in parallel, I suppose, but the issue still exists. Take this following function that interleaves both work and rewards:
Even if the miner determines they will earn the reward by mining the block, say we have an inductive function that interleaves work and rewards, and only in the base case does it send the block miner a large reward:
bond Work.doExpensiveRecursive(n: #word): #word {
if (n == 0) then
CatCoin.send($block_miner(), 10000) // massive reward upon completion
else
Work.doComputationallyExpensiveThing(n) // hard work
CatCoin.send($block_miner(), 1) // small reward, not enough to offset hard work
Work.doExpensiveRecursive(#sub(n, 1)) // interleaves work and reward
}
This requires the miner to do a lot of work before they can claim (the justifiably massive) reward to offset operational costs
People would be incentivized to make the block reward clear, so it’s unlikely to see this in practice. However, even if the rewards are ‘clear,’ there are still many ways to reward miners: any currency implemented on-chain could be used as gas.
Because there are so many different ways to send a reward, it may be hard for transaction submitters to determine how they can send a reward. What if miners expect different currencies, or specify rewards in different ways (e.g. two miners both take CatCoin
: miner 1 wants the fee specified up front, but miner 2 wants reward to be put at the end)? Would using a particular reward coin lock you to the subset of miners that use that coin?
Returning to the earlier case:
eval Bob(signature) {
CatCoin.send(@Alice, 1000)
Work.doExpensiveThing(1000000)
CatCoin.send($block_miner(), 10000)
}
Even if a miner evaluates this block, 10000
CatCoin might not be enough to recoup the work spent to reach that point. Asking a miner how much work a transaction will cost, and how much reward they’ll get for doing it, is akin to asking the miner to solve the halting problem (or at least a hard variant of it, if bonds aren’t Turing Complete.)
Aside: thinking about formalizing mining strategies
In a sense, given a function that evaluates the wealth of a miner F(World[N])
, where World[N]
is the world state after N
transactions, miners are trying to select a set of transactions T
from the pool of all potential transactions so that F(World[N+1])
is maximized.
F
could be something simple, like CatCoin.balance(@Miner)
, or altruistic, like counting the number of transactions that refactor previously-seen lambda forms into explicit names/bonds, or complex, like maximizing the value of all tokens held by @ Miner
with respect to the best exchange rates between tokens at this point in time, or malicious, like including transactions that squat on namespace or bonds full of garbage data.
An optimal strategy for maximizing F
for a set of black-box transactions T
may be to evaluate all transactions in parallel (i.e. perform one beta-reduction per transaction, repeat), subtracting the number of reductions completed (times some operational cost with respect to F
) from the amount of gas rewarded by the bond (as given by F
). Evaluation continues until either completion, in which case the miner includes the transaction in the next block, or exhaustion, in which case the miner caches the partial result (in case the transaction is included in a block mined by another miner, to reduce time spend on verification), and stops working on it.
This would incentivize transaction submitters to include payment upfront.
The thing about keeping gas separate from the transaction itself is that, if a function is gassed-out, the evaluation can be continued by adding more gas to the function. Time miners spend on evaluating functions, if only partially, will be converted into gas, allowing miners to just work on evaluating functions without caring about whether or not the computation will terminate so they can collect their reward.
If I have a function that generates prime numbers forever, if I top it off with 100 gas, I’ll get a partial evaluation with as many primes as possible for that price. Adding 100 more gas would get me that many more primes, resuming from where the function left off. If I submit a transaction that doesn’t get included because I underestimate the fees necessary, I have to duplicate my transaction instead of just putting some more gas behind it.
Anyway, assume that we agree on a common fee format or something and this is a non-issue. Here’s my actual questions:
General Questions
You mention the following, which I think is a pretty cool property to have:
Kindelia doesn’t have a built-in consensus algorithm: it is just a pure function that receives a sequence of transactions and computes a final state. As such, it relies on external networks to act as the sequencer.
So miners just have to assemble a totally-ordered list of transactions using some consensus mechanism, which is then folded through Kindelia to produce a global state.
-
What happens if a transaction is invalid? If we use, say, ethereum as a transaction sequencer, what does the network do if a sequenced transaction claims a name that has already been claimed. Is the transaction skipped? Is this impossible?—If so, how? If a type definition and a bond that uses that type definition are submitted as two transactions, what happens if the first is sequenced before the second? Is that an error? Is ordering enforced? How is ordering enforced?—Waiting for the type to be included in a block before submitting the bond?
-
What’s stopping hard work attacks? Say a malicious miner submits a transaction to factor a large product of two prime numbers. Knowing the products, he then skips the work and includes the transaction and its result in a block. The rest of the network can’t verify his solution without factoring the primes themselves (because they still have to evaluate the transaction for any side-effects, they can’t just take shortcuts here), which may clog up the network. Prime numbers are just one example, but any problem with one-way computational irreducibility could potentially be used for this attack.
-
If every node has to evaluate every transaction, aren’t we doing a lot of duplicate work? If we have this expensive computation, how can we ensure that it is executed correctly without having to execute the entire transaction itself. If we have 100 computers on the network, wouldn’t it be better evaluate 100 transactions in parallel rather that 1 transaction 100 times? This is more of an issue with blockchain construction in general—sharding is an interesting approach, but I’m afraid it may be too complex to include in the Kindelia protocol.
-
How will new nodes verify the chain from scratch? The global state is determined by evaluating a list of transactions, meaning a new node starting from zero has to evaluate each transaction in order to reach the shared global state. If we assume the network to be maximally efficient, i.e. the time it takes to evaluate transactions between blocks is equal to the time it takes to mine the next block, then the rate at which a node catches up and new blocks are mined is the same. In practice, this shouldn’t be much of an issue because there is overhead (and, assuming Moore’s Law holds, older transactions will process faster that they originally took to mine), but it’s still something to think about.
-
This is a bit of a dumb question, but: Are the miners who order transactions the same miners that evaluate transactions? If a miner includes a transaction in the block, all other miners have to evaluate it. Say we have a transaction with a lot of work and a high reward: a transaction orderer miner includes it in a block to collect the reward, without evaluating it. Who is responsible for actually ensuring transactions are evaluated?
-
What are the semantics of the
@
syntax?. Is it similar toquote
in a language like scheme, converting a name to a literal representation of its tokens? I’ve seen it used to (1) annotate ownership of a bond, (2) specify bonds as recipients of a transaction for a currency, and (3) check that the hash of a type definition matches an expected value to ensure that it has been defined. (Speaking of: if a name is not defined, what happens when one tries to use it, say to compute a hash? A compile-time type error?)
Assumptions I’d Like to Clarify
This is my mental model of how Kindelia works:
- New transactions are ordered by a consensus mechanism, the default being data-only proof-of-work, incentivized by mining fees included in the evaluation of the transaction.
- Is the just transaction definition included on-chain, or is the result of the evaluated transaction included as well? If the result is included, is it the entire result, or just the hash of the result? Is the number of beta reductions included?
- Must the transaction be evaluated before inclusion (i.e. so the result can be included), or after inclusion (the transactions just need to be ordered, whether evaluated or not)? Or does this not matter?
- After transactions have been ordered, transactions are evaluated in order, and can do one of four things:
- Declare a new name, like
CatCoin
.- (Is the size of a name only limited by block size? Is there any form of namespacing to prevent a malicious third party from registering, say
CatCoin.sendCoins
ifCatCoin
andCatCoin.send
have been registered?)
- (Is the size of a name only limited by block size? Is there any form of namespacing to prevent a malicious third party from registering, say
- Declare a new type, which is a non-generic ADT:
- (You mention a compact binary format. Is this documented somewhere?)
- Declare a new bond, which is essentially a function that can not take higher-order arguments.
- Evaluate a script, which may produce
bind
side-effects.
- Declare a new name, like
Is this correct? What am I missing?
Fin
I think that Kindelia is a cool project: to say the least, I wouldn’t spend the time writing this all out if I wasn’t interested. I’d like this project to succeed, which is why I’m raising these questions for consideration now. If they’ve already been considered, great! If not, I think there are a number of ways to address these questions, and I’m excited to see how the project evolves from here.
Thanks for taking the time to read this through, have a nice week!
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:7 (2 by maintainers)
Adding to what Kelvin said.
If you mean that nodes could be running slightly different clients with slightly different code, and believe they’re in sync, but actually not… then you’re right: there is no way to make sure you’re running the same code as everyone else. This is a good point, and maybe it is a good idea to demand, inside the Kindelia transaction function, the inclusion of the previous block state hash.
Beta reduction are no different than multiplications. It is just a primitive, with a computational cost that must be accounted for. The only problem is that, unlike MUL, it is hard to count beta reductions. But it still needs to be counted, like any other primitive operation.
Note that, while there is no computation on Ubilog, there is still a computation limit enforced by Kindelia (say, 10m rewrites/block). This limits how many transactions an Ubilog miner can include in a block, without it being dropped by the Kindelia network. So, for example, one could include two 5m-rewrites transactions, 10x 1m rewrite transactions, etc. As such, there is an incentive for miners to select transactions that maximize their rewards.
It is limited in the same way as Ethereum, by the cost of the STORE primitive. We didn’t come up with the final number yet, which is why you didn’t see it in the paper. But for the sake of example, if we agree with the 10m rewrites/block limit, then we can charge the equivalent of 10000 rewrites for a 32-bit STORE. That will set a maximum limit of 500 STOREs per second, which means the state of the network will grow, at most, 120GB per year, which is, approximately, the same limit of Ethereum.
It’s fixed. Just like on Ethereum. It is no different.
Kindelia nodes will try to evaluate all blocks/transactions up to the gas limit. The ones that exceed the limit will just be dropped. Since the limit is known and enforced by all nodes, the result will be consistent.
I should note that this is not enforced at Ubilog level. Kindelia nodes will just ignore transactions that are not valid, don’t parse, don’t typecheck etc.
You wrote on #3:
“Pure” Ubilog nodes don’t need to evaluate or enforce anything on block content. But to maximize their profits, nodes will have to evaluate/analyse transactions. Kindelia nodes will enforce the limit. So, if miners don’t account for blocks content, ensuring they are valid and profitable, the Kindelia network will just ignore those blocks, thus these miners will be wasting the opportunity to collect the rewards.
Users can publish any other kind of data to the chain. But they’ll need reward miners in other ways or mine them thenselves.
You’re right. :v