From 3c96f54f3d7de32a3b09b8d9d75dcb115ca9baad Mon Sep 17 00:00:00 2001 From: "davidad (David A. Dalrymple)" Date: Fri, 8 May 2020 21:06:27 -0500 Subject: [PATCH] Minting function maintainability (#356) --- actors/builtin/reward/reward_actor.go | 7 ++- actors/builtin/reward/reward_state.go | 66 +++++++++++++++------- actors/builtin/reward/reward_state_test.go | 20 +++++-- 3 files changed, 66 insertions(+), 27 deletions(-) diff --git a/actors/builtin/reward/reward_actor.go b/actors/builtin/reward/reward_actor.go index 7ff7fed09..8e84ee090 100644 --- a/actors/builtin/reward/reward_actor.go +++ b/actors/builtin/reward/reward_actor.go @@ -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()) diff --git a/actors/builtin/reward/reward_state.go b/actors/builtin/reward/reward_state.go index 35d7e94f5..c4a581a38 100644 --- a/actors/builtin/reward/reward_state.go +++ b/actors/builtin/reward/reward_state.go @@ -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" ) @@ -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 @@ -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 @@ -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 @@ -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) @@ -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 } @@ -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) +} diff --git a/actors/builtin/reward/reward_state_test.go b/actors/builtin/reward/reward_state_test.go index 19f8297b0..008dadd9c 100644 --- a/actors/builtin/reward/reward_state_test.go +++ b/actors/builtin/reward/reward_state_test.go @@ -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" ) @@ -26,7 +25,7 @@ import ( var mintingTestVectorPrecision = uint(90) var mintingTestVectors = []struct { - in abi.ChainEpoch + in int64 out string }{ {1051897, "135060784589637453410950129"}, @@ -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) } } }