aionnetwork / AVM

Enabling Java code to run in a blockchain environment
https://theoan.com/
MIT License
49 stars 25 forks source link

[CLOSED] Define GC transaction type and develop "deposit" billing system for storage #267

Closed aionbot closed 5 years ago

aionbot commented 5 years ago

Issue created by jeff-aion (on Monday Oct 01, 2018 at 14:18 GMT)

Now that we have a GC algorithm implementation, we need to expose this, at the top-level, and add considerations to the billing model.

The current thinking is that the GC will be a transaction type, generated by the node when crafting the block. The billing system needs to be updated to reflect the distinction between the "cost" of writing the block, versus the "deposit" which can be refunded when the block is reclaimed by the GC.

There also needs to be a "split ratio" for the deposit: how much the node is paid for the GC operation versus how much goes back to the DApp.

These options can, for the most part, just be documented constants in the fee schedule.

Still to be determined is if the deposit should be "per-byte" or "per-object". "Per-object" is probably the way that this must be done since otherwise the updates to blocks will need to adjust the deposit (which adds an increasingly unnecessary burden to full nodes) and some implementations might not know the size an orphaned value (a key-value store, for example, would need to find the unreachable keys and load their values, just for accounting - otherwise, just keeping a count of how many keys are reachable should be relatively simple in any implementation).

aionbot commented 5 years ago

Comment by jeff-aion (on Monday Oct 01, 2018 at 17:23 GMT)

Implementation plan:

  1. Create GC transaction type (moves the call to the GC out of the critical path of all calls)
  2. Change billing accounting to treat all references as a fixed value (going to start with 32 bytes)
  3. Change billing size to round up to alignment multiple (which we will start at 8 bytes)
  4. Introduce new deposit-based object allocation cost (roughly, the create cost:deposit ratio will be 1:9, and the update cost will be half of the create cost - there will be no deposit on update)
  5. Change GC to account for this (returning a negative energyUsed in TransactionResult)

Note that, for now, we will ignore the need to add the "split ratio", since we don't yet have a way to directly influence the miner reward.

aionbot commented 5 years ago

Comment by jeff-aion (on Monday Oct 01, 2018 at 22:20 GMT)

After further consideration, the idea to round the billable cost to alignment size doesn't seem to make sense. It is just more complexity while the upper-bound on this cost could more easily be managed as part of the fixed read/write cost.

This means that parts 1-3 are now done.

aionbot commented 5 years ago

Comment by jeff-aion (on Tuesday Oct 02, 2018 at 14:43 GMT)

One of the problems which may need to be addressed as part of this is the question of how we handle partially-overlapping re-entrant data sets. This problem is related to the question of how we bill for re-entrant data read/write.

The current approach that we are using ensures that the loaded set of any callee is a subset of any caller. The only objects which can exist in the callee, and not covered by the caller, are those which the callee has recently allocated (and any reachable from there will be written-back to the caller, upon return). This causes a problem where indirect loading has an unclear billing target (it loads in the callee but also incurs a load in the caller).

I suspect that #249 may point out a problem which we were trying to solve, in an overly-specialized way, for the re-entrant case but may require a more general solution. That more general solution may provide a better solution to this indirect loading approach which may eliminate this confusion in the billing system.

I will give this problem some consideration and open a new issue to track any approach to solve it.

aionbot commented 5 years ago

Comment by jeff-aion (on Tuesday Oct 02, 2018 at 18:03 GMT)

After some further thought, I don't believe that we should look at this problem the same way as #249. Instead, #269 describes how we should solve this problem. That, and #268 are effectively prerequisites to completing this item.

aionbot commented 5 years ago

Comment by jeff-aion (on Friday Oct 05, 2018 at 19:46 GMT)

Returning to this now that #249 and #269 are complete, I will approach steps 4+5, from above, in a slightly different way:

aionbot commented 5 years ago

Comment by jeff-aion (on Friday Oct 05, 2018 at 20:49 GMT)

Even this distinction between the "initial write" and "update existing" is quite complicated to detect, due to the reentrant case. We want to bill the invocation which first makes the new instance reachable from the statics. This can be a reentrant invocation. In this case, we would need to detect that a new instance was billed as its "initial" write, even though it hasn't yet been to disk and this knowledge must be written into the object in the caller's frame, somehow.

I suspect that a way to solve this problem is to create a new IPersistenceToken which is mostly equivalent to the null case, but means that it has been billed. This way, a new instance can be billed as an initial write, in the callee frame, and added to the caller's graph with this token.

Other reentrant frames could continue to pass this back to their callers' graphs while only charging the "update" fee. Only the initial storage-level frame would interpret this as though it were a null: allocating a new instanceId for the object, writing it, but still remembering only to bill it as an update.

aionbot commented 5 years ago

Comment by jeff-aion (on Tuesday Oct 09, 2018 at 04:37 GMT)

New objects initially created within the callee frame and then referenced from the caller frame, after the instances are deserialized back into that frame, introduces another problem similar to those handled in #269: We bill them in a write, in every frame, even if only touched in the callee frame.

I suspect that we can handle this with some combination of a specialized IDeserializer attached to new instances, when made reachable in a caller frame (or, more likely, equivalent logic added to the existing deserializers), a record of these new instances made visible in the frame, and a specialized IPersistenceToken to record that this is a pre-billed new instance (so we will bill the write-back as an update if this is touched in the caller frame).

I am hesitant to preemptively generalize the handling of any of these tokens so the instanceof checks to understand their meaning will persist, at least in the short-term.

aionbot commented 5 years ago

Comment by jeff-aion (on Tuesday Oct 09, 2018 at 21:17 GMT)

These new instances aren't currently being over-billed, as I had suspected, although I think that the reason for this is and incorrect change made earlier (in 319d389a01de65538189d1b48e840ef5a742ef5f): https://github.com/aionnetworkp/aion_vm/blame/master/org.aion.avm.core/src/org/aion/avm/core/persistence/ReflectionStructureCodec.java#L380

This comment is incorrect since a null is clearly not used as the next instance sink, here. Originally, that seemed like the correct answer but it caused failures in some tests which turned out to be related to new instances. I think that the handling of new instances described above is probably the more correct approach. Whether or not that needs to change in order to complete this item is not yet certain.