Skip to content
This repository has been archived by the owner on Jun 6, 2023. It is now read-only.

Minting function maintainability #356

Merged
merged 4 commits into from
May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions actors/builtin/reward/reward_actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ func (a Actor) LastPerEpochReward(rt vmr.Runtime, _ *adt.EmptyValue) *abi.TokenA

func (a Actor) computePerEpochReward(st *State, clockTime abi.ChainEpoch, networkTime abi.ChainEpoch, ticketCount int64) abi.TokenAmount {
// TODO: PARAM_FINISH
newSimpleSupply := big.Rsh(big.Mul(SimpleTotal, taylorSeriesExpansion(clockTime)), FixedPoint)
newBaselineSupply := big.Rsh(big.Mul(BaselineTotal, taylorSeriesExpansion(networkTime)), FixedPoint)
newSimpleSupply := mintingFunction(SimpleTotal, big.Lsh(big.NewInt(int64(clockTime)), MintingInputFixedPoint))

// TODO: when network time calculation produces a fixed-point representation
// with a fractional part of MintingInputFixedPoint, remove this Lsh
newBaselineSupply := mintingFunction(BaselineTotal, big.Lsh(big.NewInt(int64(networkTime)), MintingInputFixedPoint))

newSimpleMinted := big.Max(big.Sub(newSimpleSupply, st.SimpleSupply), big.Zero())
newBaselineMinted := big.Max(big.Sub(newBaselineSupply, st.BaselineSupply), big.Zero())
Expand Down
66 changes: 46 additions & 20 deletions actors/builtin/reward/reward_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

abi "github.com/filecoin-project/specs-actors/actors/abi"
big "github.com/filecoin-project/specs-actors/actors/abi/big"
builtin "github.com/filecoin-project/specs-actors/actors/builtin"
adt "github.com/filecoin-project/specs-actors/actors/util/adt"
)

Expand Down Expand Up @@ -63,13 +64,24 @@ func ConstructState(emptyMultiMapCid cid.Cid) *State {
// have resorted to using an ad-hoc fixed-point standard. Experimental
// testing with the relevant scales of inputs and with a desired "atto"
// level of output precision yielded a recommendation of a 97-bit fractional
// part, which was stored in the constant "FixedPoint".
// !IMPORTANT!: the return value from this function is a factor of 2^FixedPoint
// greater than the number it is semantically intended to represent (which
// will always be between 0 and 1). The expectation is that callers will
// multiply the result by some number, and THEN right-shift the result of
// the multiplication by FixedPoint bits, thus implementing fixed-point
// multiplication by the returned fraction.
// part, which was stored in the constant "MintingOutputFixedPoint".
// Fractional input is only necessary when considering "effective network
// time"; there, the desired precision is determined by the minimum
// plausible ratio between realized network power and network baseline,
// which is set in "MintingInputFixedPoint".
//
// !IMPORTANT!: the time input to this function should be a factor of
// 2^MintingInputFixedPoint greater than the semantically intended value, and
// the return value from this function is a factor of 2^MintingOutputFixedPoint
// greater. The semantics of the output value will always be a fraction
// between 0 and 1, but will be represented as an integer between 0 and
// 2^FixedPoint. The expectation is that callers will multiply the result by
// some number, and THEN right-shift the result of the multiplication by
// FixedPoint bits, thus implementing fixed-point multiplication by the
// returned fraction. Analogously, if callers intend to pass in an integer
// "t", it should be left-shifted by MintingInputFixedPoint before being
// passed; if it is fractional, its fractional part should be
// MintingInputFixedPoint bits long.
//
// 2. Since we do not have a math library in this setting, we cannot directly
// implement the intended closed form using stock implementations of
Expand Down Expand Up @@ -131,12 +143,11 @@ func ConstructState(emptyMultiMapCid cid.Cid) *State {
// from both the numerator and denominator accumulators to control the
// computational complexity of the bigint multiplications.

// Fixed-point precision (in bits) used internally and for output
const FixedPoint = 97
// Fixed-point precision (in bits) used for minting function's input "t"
const MintingInputFixedPoint = 30

// Used in the definition of λ
const BlockTimeSeconds = 30
const SecondsInYear = 31556925
// Fixed-point precision (in bits) used internally and for output
const MintingOutputFixedPoint = 97

// The following are the numerator and denominator of -ln(1/2)=ln(2),
// represented as a rational with sufficient precision. They are parsed from
Expand All @@ -148,18 +159,19 @@ var LnTwoDen, _ = big.FromString("10000000000000000000000000000")
// We multiply the fraction ([Seconds per epoch] / (6 * [Seconds per year]))
// into the rational representation of -ln(1/2) which was just loaded, to
// produce the final, constant, rational representation of λ.
var LambdaNum = big.Mul(big.NewInt(BlockTimeSeconds), LnTwoNum)
var LambdaDen = big.Mul(big.NewInt(6*SecondsInYear), LnTwoDen)
var LambdaNum = big.Mul(big.NewInt(builtin.EpochDurationSeconds), LnTwoNum)
var LambdaDen = big.Mul(big.NewInt(6*builtin.SecondsInYear), LnTwoDen)

// This function implements f(t) as described in the large comment block above,
// with the important caveat that its return value must not be interpreted
// semantically as an integer, but rather as a fixed-point number with
// FixedPoint bits of fractional part.
func taylorSeriesExpansion(t abi.ChainEpoch) big.Int {
func taylorSeriesExpansion(lambdaNum big.Int, lambdaDen big.Int, t big.Int) big.Int {
// `numeratorBase` is the numerator of the rational representation of (-λt).
numeratorBase := big.Mul(LambdaNum.Neg(), big.NewInt(int64(t)))
// The denominator of (-λt) is simply the denominator of λ, as -t is integral.
denominatorBase := LambdaDen
numeratorBase := big.Mul(lambdaNum.Neg(), t)
// The denominator of (-λt) is the denominator of λ times the denominator of t,
// which is a fixed 2^MintingInputFixedPoint. Multiplying by this is a left shift.
denominatorBase := big.Lsh(lambdaDen, MintingInputFixedPoint)

// `numerator` is the accumulator for numerators of the series terms. The
// first term is simply (-1)(-λt). To include that factor of (-1), which
Expand All @@ -184,7 +196,7 @@ func taylorSeriesExpansion(t abi.ChainEpoch) big.Int {
denominator = big.Mul(denominator, big.NewInt(n))

// Left-shift and divide to convert rational into fixed-point.
term := big.Div(big.Lsh(numerator, FixedPoint), denominator)
term := big.Div(big.Lsh(numerator, MintingOutputFixedPoint), denominator)

// Accumulate the fixed-point result into the return accumulator.
ret = big.Add(ret, term)
Expand All @@ -201,7 +213,7 @@ func taylorSeriesExpansion(t abi.ChainEpoch) big.Int {
// by the same number of bits, all we have done is lose unnecessary
// precision that would slow down the next iteration's multiplies.
denominatorLen := big.BitLen(denominator)
unnecessaryBits := denominatorLen - FixedPoint
unnecessaryBits := denominatorLen - MintingOutputFixedPoint
if unnecessaryBits < 0 {
unnecessaryBits = 0
}
Expand All @@ -212,3 +224,17 @@ func taylorSeriesExpansion(t abi.ChainEpoch) big.Int {

return ret
}

// Minting Function Wrapper
//
// Intent
// The necessary calling conventions for the function above are unwieldy:
// the common case is to supply the canonical Lambda, multiply by some other
// number, and right-shift down by MintingOutputFixedPoint. This convenience
// wrapper implements those conventions. However, it does NOT implement
// left-shifting the input by the MintingInputFixedPoint, because baseline
// minting will (soon) actually supply a fractional input, so this would only
// be used for simple minting.
func mintingFunction(factor big.Int, t big.Int) big.Int {
return big.Rsh(big.Mul(factor, taylorSeriesExpansion(LambdaNum, LambdaDen, t)), MintingOutputFixedPoint)
}
20 changes: 15 additions & 5 deletions actors/builtin/reward/reward_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package reward
import (
"testing"

"github.com/filecoin-project/specs-actors/actors/abi"
"github.com/filecoin-project/specs-actors/actors/abi/big"
)

Expand All @@ -26,7 +25,7 @@ import (
var mintingTestVectorPrecision = uint(90)

var mintingTestVectors = []struct {
in abi.ChainEpoch
in int64
out string
}{
{1051897, "135060784589637453410950129"},
Expand All @@ -38,20 +37,31 @@ var mintingTestVectors = []struct {
{7363279, "686500230252085183344830372"},
}

const SecondsInYear = 31556925
const TestEpochDurationSeconds = 30

var TestLambdaNum = big.Mul(big.NewInt(TestEpochDurationSeconds), LnTwoNum)
var TestLambdaDen = big.Mul(big.NewInt(6*SecondsInYear), LnTwoDen)

func TestMintingFunction(t *testing.T) {
for _, vector := range mintingTestVectors {
ts_output := taylorSeriesExpansion(vector.in)
// In order to supply an integer as an input to the minting function, we
// first left-shift zeroes into the fractional part of its fixed-point
// representation.
ts_input := big.Lsh(big.NewInt(vector.in), MintingInputFixedPoint)

ts_output := taylorSeriesExpansion(TestLambdaNum, TestLambdaDen, ts_input)

// ts_output will always range between 0 and 2^FixedPoint. If we
// right-shifted by FixedPoint, without first multiplying by something, we
// would discard _all_ the bits and get 0. Instead, we want to discard only
// those bits in the FixedPoint representation that we don't also want to
// require to exactly match the test vectors.
ts_truncated_fractional_part := big.Rsh(ts_output, FixedPoint-mintingTestVectorPrecision)
ts_truncated_fractional_part := big.Rsh(ts_output, MintingOutputFixedPoint-mintingTestVectorPrecision)

expected_truncated_fractional_part, _ := big.FromString(vector.out)
if !(ts_truncated_fractional_part.Equals(expected_truncated_fractional_part)) {
t.Errorf("at epoch %q, computed supply %q, expected supply %q", vector.in, ts_truncated_fractional_part, expected_truncated_fractional_part)
t.Errorf("minting function: on input %q, computed %q, expected %q", ts_input, ts_truncated_fractional_part, expected_truncated_fractional_part)
}
}
}