diff --git a/actors/builtin/miner/monies.go b/actors/builtin/miner/monies.go index 87b4086f6..932272f4a 100644 --- a/actors/builtin/miner/monies.go +++ b/actors/builtin/miner/monies.go @@ -23,7 +23,7 @@ var InitialPledgeProjectionPeriod = abi.ChainEpoch(InitialPledgeFactor) * builti // Cap on initial pledge requirement for sectors. // The target is 1 FIL (10**18 attoFIL) per 32GiB. // This does not divide evenly, so the result is fractionally smaller. -var InitialPledgeMaxPerByte = big.Div(big.NewInt(1e18), big.NewInt(32 << 30)) +var InitialPledgeMaxPerByte = big.Div(big.NewInt(1e18), big.NewInt(32<<30)) // Multiplier of share of circulating money supply for consensus pledge required to commit a sector. // This pledge is lost if a sector is terminated before its full committed lifetime. @@ -67,7 +67,7 @@ func ExpectedRewardForPower(rewardEstimate, networkQAPowerEstimate smoothing.Fil } expectedRewardForProvingPeriod := smoothing.ExtrapolatedCumSumOfRatio(projectionDuration, 0, rewardEstimate, networkQAPowerEstimate) br128 := big.Mul(qaSectorPower, expectedRewardForProvingPeriod) // Q.0 * Q.128 => Q.128 - br := big.Rsh(br128, math.Precision) + br := big.Rsh(br128, math.Precision128) return big.Max(br, big.Zero()) // negative BR is clamped at 0 } diff --git a/actors/builtin/reward/cbor_gen.go b/actors/builtin/reward/cbor_gen.go index b8091c9de..44557474c 100644 --- a/actors/builtin/reward/cbor_gen.go +++ b/actors/builtin/reward/cbor_gen.go @@ -13,7 +13,7 @@ import ( var _ = xerrors.Errorf -var lengthBufState = []byte{137} +var lengthBufState = []byte{139} func (t *State) MarshalCBOR(w io.Writer) error { if t == nil { @@ -82,6 +82,16 @@ func (t *State) MarshalCBOR(w io.Writer) error { if err := t.TotalStoragePowerReward.MarshalCBOR(w); err != nil { return err } + + // t.SimpleTotal (big.Int) (struct) + if err := t.SimpleTotal.MarshalCBOR(w); err != nil { + return err + } + + // t.BaselineTotal (big.Int) (struct) + if err := t.BaselineTotal.MarshalCBOR(w); err != nil { + return err + } return nil } @@ -99,7 +109,7 @@ func (t *State) UnmarshalCBOR(r io.Reader) error { return fmt.Errorf("cbor input should be of type array") } - if extra != 9 { + if extra != 11 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -215,6 +225,24 @@ func (t *State) UnmarshalCBOR(r io.Reader) error { return xerrors.Errorf("unmarshaling t.TotalStoragePowerReward: %w", err) } + } + // t.SimpleTotal (big.Int) (struct) + + { + + if err := t.SimpleTotal.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.SimpleTotal: %w", err) + } + + } + // t.BaselineTotal (big.Int) (struct) + + { + + if err := t.BaselineTotal.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.BaselineTotal: %w", err) + } + } return nil } diff --git a/actors/builtin/reward/reward_logic.go b/actors/builtin/reward/reward_logic.go index 3ee6a0569..7466989b5 100644 --- a/actors/builtin/reward/reward_logic.go +++ b/actors/builtin/reward/reward_logic.go @@ -33,21 +33,23 @@ var BaselineInitialValue = big.NewInt(2_888_888_880_000_000_000) // Q.0 // Initialize baseline power for epoch -1 so that baseline power at epoch 0 is // BaselineInitialValue. func InitBaselinePower() abi.StoragePower { - baselineInitialValue256 := big.Lsh(BaselineInitialValue, 2*math.Precision) // Q.0 => Q.256 + baselineInitialValue256 := big.Lsh(BaselineInitialValue, 2*math.Precision128) // Q.0 => Q.256 baselineAtMinusOne := big.Div(baselineInitialValue256, BaselineExponent) // Q.256 / Q.128 => Q.128 - return big.Rsh(baselineAtMinusOne, math.Precision) // Q.128 => Q.0 + return big.Rsh(baselineAtMinusOne, math.Precision128) // Q.128 => Q.0 } // Compute BaselinePower(t) from BaselinePower(t-1) with an additional multiplication // of the base exponent. func BaselinePowerFromPrev(prevEpochBaselinePower abi.StoragePower) abi.StoragePower { thisEpochBaselinePower := big.Mul(prevEpochBaselinePower, BaselineExponent) // Q.0 * Q.128 => Q.128 - return big.Rsh(thisEpochBaselinePower, math.Precision) // Q.128 => Q.0 + return big.Rsh(thisEpochBaselinePower, math.Precision128) // Q.128 => Q.0 } -// These numbers are placeholders, but should be in units of attoFIL, 10^-18 FIL -var SimpleTotal = big.Mul(big.NewInt(330e6), big.NewInt(1e18)) // 330M -var BaselineTotal = big.Mul(big.NewInt(770e6), big.NewInt(1e18)) // 770M +// These numbers are estimates of the onchain constants. They are good for initializing state in +// devnets and testing but will not match the on chain values exactly which depend on storage onboarding +// and upgrade epoch history. They are in units of attoFIL, 10^-18 FIL +var DefaultSimpleTotal = big.Mul(big.NewInt(330e6), big.NewInt(1e18)) // 330M +var DefaultBaselineTotal = big.Mul(big.NewInt(770e6), big.NewInt(1e18)) // 770M // Computes RewardTheta which is is precise fractional value of effectiveNetworkTime. // The effectiveNetworkTime is defined by CumsumBaselinePower(theta) == CumsumRealizedPower @@ -55,13 +57,13 @@ var BaselineTotal = big.Mul(big.NewInt(770e6), big.NewInt(1e18)) // 770M // we perform linear interpolation between CumsumBaseline(⌊theta⌋) and CumsumBaseline(⌈theta⌉). // The effectiveNetworkTime argument is ceiling of theta. // The result is a fractional effectiveNetworkTime (theta) in Q.128 format. -func computeRTheta(effectiveNetworkTime abi.ChainEpoch, baselinePowerAtEffectiveNetworkTime, cumsumRealized, cumsumBaseline big.Int) big.Int { +func ComputeRTheta(effectiveNetworkTime abi.ChainEpoch, baselinePowerAtEffectiveNetworkTime, cumsumRealized, cumsumBaseline big.Int) big.Int { var rewardTheta big.Int if effectiveNetworkTime != 0 { rewardTheta = big.NewInt(int64(effectiveNetworkTime)) // Q.0 - rewardTheta = big.Lsh(rewardTheta, math.Precision) // Q.0 => Q.128 + rewardTheta = big.Lsh(rewardTheta, math.Precision128) // Q.0 => Q.128 diff := big.Sub(cumsumBaseline, cumsumRealized) - diff = big.Lsh(diff, math.Precision) // Q.0 => Q.128 + diff = big.Lsh(diff, math.Precision128) // Q.0 => Q.128 diff = big.Div(diff, baselinePowerAtEffectiveNetworkTime) // Q.128 / Q.0 => Q.128 rewardTheta = big.Sub(rewardTheta, diff) // Q.128 } else { @@ -75,42 +77,42 @@ var ( // lambda = ln(2) / (6 * epochsInYear) // for Q.128: int(lambda * 2^128) // Calculation here: https://www.wolframalpha.com/input/?i=IntegerPart%5BLog%5B2%5D+%2F+%286+*+%281+year+%2F+30+seconds%29%29+*+2%5E128%5D - lambda = big.MustFromString("37396271439864487274534522888786") + Lambda = big.MustFromString("37396271439864487274534522888786") // expLamSubOne = e^lambda - 1 // for Q.128: int(expLamSubOne * 2^128) // Calculation here: https://www.wolframalpha.com/input/?i=IntegerPart%5B%5BExp%5BLog%5B2%5D+%2F+%286+*+%281+year+%2F+30+seconds%29%29%5D+-+1%5D+*+2%5E128%5D - expLamSubOne = big.MustFromString("37396273494747879394193016954629") + ExpLamSubOne = big.MustFromString("37396273494747879394193016954629") ) // Computes a reward for all expected leaders when effective network time changes from prevTheta to currTheta // Inputs are in Q.128 format -func computeReward(epoch abi.ChainEpoch, prevTheta, currTheta big.Int) abi.TokenAmount { - simpleReward := big.Mul(SimpleTotal, expLamSubOne) //Q.0 * Q.128 => Q.128 - epochLam := big.Mul(big.NewInt(int64(epoch)), lambda) // Q.0 * Q.128 => Q.128 +func computeReward(epoch abi.ChainEpoch, prevTheta, currTheta, simpleTotal, baselineTotal big.Int) abi.TokenAmount { + simpleReward := big.Mul(simpleTotal, ExpLamSubOne) //Q.0 * Q.128 => Q.128 + epochLam := big.Mul(big.NewInt(int64(epoch)), Lambda) // Q.0 * Q.128 => Q.128 - simpleReward = big.Mul(simpleReward, big.NewFromGo(expneg(epochLam.Int))) // Q.128 * Q.128 => Q.256 - simpleReward = big.Rsh(simpleReward, math.Precision) // Q.256 >> 128 => Q.128 + simpleReward = big.Mul(simpleReward, big.NewFromGo(math.ExpNeg(epochLam.Int))) // Q.128 * Q.128 => Q.256 + simpleReward = big.Rsh(simpleReward, math.Precision128) // Q.256 >> 128 => Q.128 - baselineReward := big.Sub(computeBaselineSupply(currTheta), computeBaselineSupply(prevTheta)) // Q.128 + baselineReward := big.Sub(computeBaselineSupply(currTheta, baselineTotal), computeBaselineSupply(prevTheta, baselineTotal)) // Q.128 reward := big.Add(simpleReward, baselineReward) // Q.128 - return big.Rsh(reward, math.Precision) // Q.128 => Q.0 + return big.Rsh(reward, math.Precision128) // Q.128 => Q.0 } // Computes baseline supply based on theta in Q.128 format. // Return is in Q.128 format -func computeBaselineSupply(theta big.Int) big.Int { - thetaLam := big.Mul(theta, lambda) // Q.128 * Q.128 => Q.256 - thetaLam = big.Rsh(thetaLam, math.Precision) // Q.256 >> 128 => Q.128 +func computeBaselineSupply(theta, baselineTotal big.Int) big.Int { + thetaLam := big.Mul(theta, Lambda) // Q.128 * Q.128 => Q.256 + thetaLam = big.Rsh(thetaLam, math.Precision128) // Q.256 >> 128 => Q.128 - eTL := big.NewFromGo(expneg(thetaLam.Int)) // Q.128 + eTL := big.NewFromGo(math.ExpNeg(thetaLam.Int)) // Q.128 one := big.NewInt(1) - one = big.Lsh(one, math.Precision) // Q.0 => Q.128 + one = big.Lsh(one, math.Precision128) // Q.0 => Q.128 oneSub := big.Sub(one, eTL) // Q.128 - return big.Mul(BaselineTotal, oneSub) // Q.0 * Q.128 => Q.128 + return big.Mul(baselineTotal, oneSub) // Q.0 * Q.128 => Q.128 } // SlowConvenientBaselineForEpoch computes baseline power for use in epoch t diff --git a/actors/builtin/reward/reward_logic_test.go b/actors/builtin/reward/reward_logic_test.go index f6d7381e2..98ebd2423 100644 --- a/actors/builtin/reward/reward_logic_test.go +++ b/actors/builtin/reward/reward_logic_test.go @@ -16,7 +16,7 @@ import ( func q128ToF(x big.Int) float64 { q128 := new(gbig.Int).SetInt64(1) - q128 = q128.Lsh(q128, math.Precision) + q128 = q128.Lsh(q128, math.Precision128) res, _ := new(gbig.Rat).SetFrac(x.Int, q128).Float64() return res } @@ -26,14 +26,14 @@ func TestComputeRTeta(t *testing.T) { return big.Mul(big.NewInt(int64(epoch+1)), big.NewInt(2048)) } - assert.Equal(t, 0.5, q128ToF(computeRTheta(1, baselinePowerAt(1), big.NewInt(2048+2*2048*0.5), big.NewInt(2048+2*2048)))) - assert.Equal(t, 0.25, q128ToF(computeRTheta(1, baselinePowerAt(1), big.NewInt(2048+2*2048*0.25), big.NewInt(2048+2*2048)))) + assert.Equal(t, 0.5, q128ToF(ComputeRTheta(1, baselinePowerAt(1), big.NewInt(2048+2*2048*0.5), big.NewInt(2048+2*2048)))) + assert.Equal(t, 0.25, q128ToF(ComputeRTheta(1, baselinePowerAt(1), big.NewInt(2048+2*2048*0.25), big.NewInt(2048+2*2048)))) cumsum15 := big.NewInt(0) for i := abi.ChainEpoch(0); i < 16; i++ { cumsum15 = big.Add(cumsum15, baselinePowerAt(i)) } - assert.Equal(t, 15.25, q128ToF(computeRTheta(16, + assert.Equal(t, 15.25, q128ToF(ComputeRTheta(16, baselinePowerAt(16), big.Add(cumsum15, big.Div(baselinePowerAt(16), big.NewInt(4))), big.Add(cumsum15, baselinePowerAt(16))))) @@ -41,11 +41,11 @@ func TestComputeRTeta(t *testing.T) { func TestBaselineReward(t *testing.T) { step := gbig.NewInt(5000) - step = step.Lsh(step, math.Precision) + step = step.Lsh(step, math.Precision128) step = step.Sub(step, gbig.NewInt(77777777777)) // offset from full integers delta := gbig.NewInt(1) - delta = delta.Lsh(delta, math.Precision) + delta = delta.Lsh(delta, math.Precision128) delta = delta.Sub(delta, gbig.NewInt(33333333333)) // offset from full integers prevTheta := new(gbig.Int) @@ -53,10 +53,10 @@ func TestBaselineReward(t *testing.T) { b := &bytes.Buffer{} b.WriteString("t0, t1, y\n") - simple := computeReward(0, big.Zero(), big.Zero()) + simple := computeReward(0, big.Zero(), big.Zero(), DefaultSimpleTotal, DefaultBaselineTotal) for i := 0; i < 512; i++ { - reward := computeReward(0, big.NewFromGo(prevTheta), big.NewFromGo(theta)) + reward := computeReward(0, big.NewFromGo(prevTheta), big.NewFromGo(theta), DefaultSimpleTotal, DefaultBaselineTotal) reward = big.Sub(reward, simple) fmt.Fprintf(b, "%s,%s,%s\n", prevTheta, theta, reward.Int) prevTheta = prevTheta.Add(prevTheta, step) @@ -71,7 +71,7 @@ func TestSimpleReward(t *testing.T) { b.WriteString("x, y\n") for i := int64(0); i < 512; i++ { x := i * 5000 - reward := computeReward(abi.ChainEpoch(x), big.Zero(), big.Zero()) + reward := computeReward(abi.ChainEpoch(x), big.Zero(), big.Zero(), DefaultSimpleTotal, DefaultBaselineTotal) fmt.Fprintf(b, "%d,%s\n", x, reward.Int) } diff --git a/actors/builtin/reward/reward_state.go b/actors/builtin/reward/reward_state.go index acdcb6ee2..ea0658df6 100644 --- a/actors/builtin/reward/reward_state.go +++ b/actors/builtin/reward/reward_state.go @@ -54,6 +54,14 @@ type State struct { // TotalStoragePowerReward tracks the total FIL awarded to block miners TotalStoragePowerReward abi.TokenAmount + + // Simple and Baseline totals are constants used for computing rewards. + // They are on chain because of a historical fix resetting baseline value + // in a way that depended on the history leading immediately up to the + // migration fixing the value. These values can be moved from state back + // into a code constant in a subsequent upgrade. + SimpleTotal abi.TokenAmount + BaselineTotal abi.TokenAmount } func ConstructState(currRealizedPower abi.StoragePower) *State { @@ -69,6 +77,9 @@ func ConstructState(currRealizedPower abi.StoragePower) *State { ThisEpochRewardSmoothed: smoothing.NewEstimate(InitialRewardPositionEstimate, InitialRewardVelocityEstimate), TotalStoragePowerReward: big.Zero(), + + SimpleTotal: DefaultSimpleTotal, + BaselineTotal: DefaultBaselineTotal, } st.updateToNextEpochWithReward(currRealizedPower) @@ -94,11 +105,11 @@ func (st *State) updateToNextEpoch(currRealizedPower abi.StoragePower) { // Takes in a current realized power for a reward epoch and computes // and updates reward state to track reward for the next epoch func (st *State) updateToNextEpochWithReward(currRealizedPower abi.StoragePower) { - prevRewardTheta := computeRTheta(st.EffectiveNetworkTime, st.EffectiveBaselinePower, st.CumsumRealized, st.CumsumBaseline) + prevRewardTheta := ComputeRTheta(st.EffectiveNetworkTime, st.EffectiveBaselinePower, st.CumsumRealized, st.CumsumBaseline) st.updateToNextEpoch(currRealizedPower) - currRewardTheta := computeRTheta(st.EffectiveNetworkTime, st.EffectiveBaselinePower, st.CumsumRealized, st.CumsumBaseline) + currRewardTheta := ComputeRTheta(st.EffectiveNetworkTime, st.EffectiveBaselinePower, st.CumsumRealized, st.CumsumBaseline) - st.ThisEpochReward = computeReward(st.Epoch, prevRewardTheta, currRewardTheta) + st.ThisEpochReward = computeReward(st.Epoch, prevRewardTheta, currRewardTheta, st.SimpleTotal, st.BaselineTotal) } func (st *State) updateSmoothedEstimates(delta abi.ChainEpoch) { diff --git a/actors/migration/reward.go b/actors/migration/reward.go index 10035213a..b63e00364 100644 --- a/actors/migration/reward.go +++ b/actors/migration/reward.go @@ -3,34 +3,153 @@ package migration import ( "context" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" reward2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/reward" + "github.com/filecoin-project/specs-actors/v2/actors/states" + math2 "github.com/filecoin-project/specs-actors/v2/actors/util/math" smoothing2 "github.com/filecoin-project/specs-actors/v2/actors/util/smoothing" ) +var ( + // Q.128 coefficients used to compute new effective network time + // (g*Tau is the BaselineExponent, B0 is the BaselineInitialValue) + gTauOverB0 big.Int + oneOverGTau big.Int +) + +func init() { + constStrs := []string{ + "77669179383316", + "516058975841646949912583456815941211282956743", + } + constBigs := math2.Parse(constStrs) + gTauOverB0 = big.NewFromGo(constBigs[0]) // Q.128 + oneOverGTau = big.NewFromGo(constBigs[1]) +} + type rewardMigrator struct { + actorsOut *states.Tree } -func (m *rewardMigrator) MigrateState(ctx context.Context, store cbor.IpldStore, head cid.Cid, _ MigrationInfo) (*StateMigrationResult, error) { +func (m *rewardMigrator) MigrateState(ctx context.Context, store cbor.IpldStore, head cid.Cid, migInfo MigrationInfo) (*StateMigrationResult, error) { var inState reward0.State if err := store.Get(ctx, head, &inState); err != nil { return nil, err } + // Get migrated power actor's ThisEpochRawBytePower + outPowerAct, found, err := m.actorsOut.GetActor(builtin2.StoragePowerActorAddr) + if err != nil { + return nil, err + } + if !found { + return nil, xerrors.Errorf("could not find power actor in migrated state") + } + var outPowerSt power2.State + if err := store.Get(ctx, outPowerAct.Head, &outPowerSt); err != nil { + return nil, xerrors.Errorf("failed to read migrated power actor state %w", err) + } + thisEpochRawBytePower := outPowerSt.ThisEpochRawBytePower + + one := big.Lsh(big.NewInt(1), math2.Precision128) // Q.128 + // Set baseline to the value it would be at the end of the prior epoch if + // current parameters had been in place at network start + outThisEpochBaselinePower := math2.ExpBySquaring(reward2.BaselineExponent, int64(migInfo.priorEpoch)) // Q.128 + outThisEpochBaselinePower = big.Mul(reward2.BaselineInitialValue, outThisEpochBaselinePower) // Q.0 * Q.128 => Q.128 + outThisEpochBaselinePower = big.Rsh(outThisEpochBaselinePower, math2.Precision128) // Q.128 => Q.0 + + // Set effective network power to the value it would be at the end of the + // prior epoch if the current parameters had been in place at network start + cumSumRealized := big.Lsh(inState.CumsumRealized, math2.Precision128) // Q.0 => Q.128 + acc0 := big.Mul(cumSumRealized, gTauOverB0) // Q.128 * Q.128 => Q.256 + acc0 = big.Rsh(acc0, math2.Precision128) // Q.256 => Q.128 + acc0 = big.Add(acc0, one) + acc0 = math2.Ln(acc0) + acc0 = big.Mul(acc0, oneOverGTau) // Q.128 * Q.128 => Q.256 + postUpgradeTheta := big.Rsh(acc0, math2.Precision128) // Q.256 => Q.128 + effNetworkTime := big.Rsh(postUpgradeTheta, math2.Precision128) // Q.128 => Q.0 + effNetworkTime = big.Add(effNetworkTime, big.NewInt(1)) + if !effNetworkTime.Int.IsInt64() { + return nil, xerrors.Errorf("new effective network time chain epoch out of bounds %v", effNetworkTime) + } + outEffectiveNetworkTime := abi.ChainEpoch(effNetworkTime.Int.Int64()) + + // Set effective baseline power given the new effective network time and + // baseline function. + outEffectiveBaselinePower := math2.ExpBySquaring(reward2.BaselineExponent, int64(outEffectiveNetworkTime)) // Q.128 + outEffectiveBaselinePower128 := big.Mul(reward2.BaselineInitialValue, outEffectiveBaselinePower) // Q.0 * Q.128 => Q.128 + outEffectiveBaselinePower = big.Rsh(outEffectiveBaselinePower128, math2.Precision128) // Q.128 => Q.0 + + // Set cumsum baseline given new effective network time and baseline function + outCumSumBaseline := big.Sub(outEffectiveBaselinePower128, big.Lsh(reward2.BaselineInitialValue, math2.Precision128)) // Q.128 - Q.128 => Q.128 + outCumSumBaseline = big.Mul(outCumSumBaseline, oneOverGTau) // Q.128 * Q.128 => Q.256 + outCumSumBaseline = big.Rsh(outCumSumBaseline, 2*math2.Precision128) // Q.256 => Q.0 + + // Reduce baseline total so that the amount of tokens remaining to be minted is held fixed across the upgrade + preUpgradeTheta := reward2.ComputeRTheta(inState.EffectiveNetworkTime, inState.EffectiveBaselinePower, inState.CumsumRealized, inState.CumsumBaseline) // Q.128 + thetaDiff := big.Sub(preUpgradeTheta, postUpgradeTheta) + baselineAdjustmentExp := big.Mul(reward2.Lambda, thetaDiff) // Q.128 * Q.128 => Q.256 + baselineAdjustmentExp = big.Rsh(baselineAdjustmentExp, math2.Precision128) // Q.256 => Q.128 + baselineAdjustment := big.NewFromGo(math2.ExpNeg(baselineAdjustmentExp.Int)) + outBaselineTotal := big.Mul(reward0.BaselineTotal, baselineAdjustment) // Q.0 * Q.128 => Q.128 + outBaselineTotal = big.Rsh(outBaselineTotal, math2.Precision128) // Q.128 => Q.0 + + // Set reward filter postiion and velocity values + + // Filter position is set by evaluating the closed form expression for expected + // block reward at a particular epoch assuming that the new baseline function + // had been in place at network start + acc1 := big.Mul(reward2.BaselineInitialValue, oneOverGTau) // Q.0 * Q.128 => Q.128 + acc1 = big.Add(acc1, big.Lsh(inState.CumsumRealized, math2.Precision128)) + rawBytePower256 := big.Lsh(thisEpochRawBytePower, 2*math2.Precision128) // Q.0 => Q.256 + acc1 = big.Div(rawBytePower256, acc1) // Q.256 / Q.128 => Q.128 + acc1 = big.Add(acc1, one) + acc1 = math2.Ln(acc1) + acc1 = big.Lsh(acc1, math2.Precision128) // Q.128 => Q.256 + acc1 = big.Div(acc1, big.Lsh(big.NewInt(6), math2.Precision128)) // Q.256 / Q.128 => Q.128 + acc1 = big.NewFromGo(math2.ExpNeg(acc1.Int)) + acc1 = big.Sub(one, acc1) + acc2 := big.Mul(postUpgradeTheta, reward2.Lambda) // Q.128 * Q.128 => Q.256 + acc2 = big.Rsh(acc2, math2.Precision128) // Q.256 => Q.128 + acc2 = big.NewFromGo(math2.ExpNeg(acc2.Int)) + acc1 = big.Mul(acc1, acc2) //Q.128 * Q.128 => Q.256 + acc1 = big.Rsh(acc1, math2.Precision128) // Q.256 => Q.128 + acc1 = big.Mul(acc1, outBaselineTotal) // Q.128 * Q.0 => Q.128 + acc3 := big.Mul(big.NewInt(int64(migInfo.priorEpoch)), reward2.Lambda) // Q.0 * Q.128 => Q.128 + acc3 = big.NewFromGo(math2.ExpNeg(acc3.Int)) + acc3 = big.Mul(acc3, reward0.SimpleTotal) // Q.128 * Q.0 => Q.128 + acc3 = big.Mul(acc3, reward2.ExpLamSubOne) // Q.128 * Q.128 => Q.256 + acc3 = big.Rsh(acc3, math2.Precision128) // Q.256 => Q.128 + outRewardSmoothedPosition := big.Add(acc1, acc3) + + // Filter velocity precision is less important. Hard coded value of 31 x 10^51 is sufficient + outRewardSmoothedVelocity := big.Mul( + big.NewInt(31), + big.Exp(big.NewInt(10), big.NewInt(51)), + ) // Q.128 + outThisEpochRewardSmoothed := smoothing2.NewEstimate(outRewardSmoothedPosition, outRewardSmoothedVelocity) + outState := reward2.State{ - CumsumBaseline: inState.CumsumBaseline, + CumsumBaseline: outCumSumBaseline, CumsumRealized: inState.CumsumRealized, - EffectiveNetworkTime: inState.EffectiveNetworkTime, - EffectiveBaselinePower: inState.EffectiveBaselinePower, + EffectiveNetworkTime: outEffectiveNetworkTime, + EffectiveBaselinePower: outEffectiveBaselinePower, ThisEpochReward: inState.ThisEpochReward, - ThisEpochRewardSmoothed: smoothing2.FilterEstimate(*inState.ThisEpochRewardSmoothed), - ThisEpochBaselinePower: inState.ThisEpochBaselinePower, + ThisEpochRewardSmoothed: outThisEpochRewardSmoothed, + ThisEpochBaselinePower: outThisEpochBaselinePower, Epoch: inState.Epoch, TotalStoragePowerReward: inState.TotalMined, + SimpleTotal: reward0.SimpleTotal, + BaselineTotal: outBaselineTotal, } newHead, err := store.Put(ctx, &outState) return &StateMigrationResult{ diff --git a/actors/migration/top.go b/actors/migration/top.go index 5a5787dbd..9b9774251 100644 --- a/actors/migration/top.go +++ b/actors/migration/top.go @@ -139,6 +139,7 @@ var migrations = map[cid.Cid]ActorMigration{ // nolint:varcheck,deadcode,unused var deferredMigrations = map[cid.Cid]bool{ builtin0.VerifiedRegistryActorCodeID: true, builtin0.StoragePowerActorCodeID: true, + builtin0.RewardActorCodeID: true, } // Migrates the filecoin state tree starting from the global state tree and upgrading all actor state. @@ -268,6 +269,35 @@ func MigrateStateTree(ctx context.Context, store cbor.IpldStore, stateRootIn cid return cid.Undef, err } + // Migrate reward actor + rm := migrations[builtin0.RewardActorCodeID].StateMigration.(*rewardMigrator) + rm.actorsOut = actorsOut + rewardActorIn, found, err := actorsIn.GetActor(builtin0.RewardActorAddr) + if err != nil { + return cid.Undef, err + } + if !found { + return cid.Undef, xerrors.Errorf("could not find reward actor in state") + } + rewardResult, err := rm.MigrateState(ctx, store, rewardActorIn.Head, MigrationInfo{ + address: builtin0.RewardActorAddr, + balance: rewardActorIn.Balance, + priorEpoch: priorEpoch, + }) + if err != nil { + return cid.Undef, err + } + rewardActorOut := states.Actor{ + Code: builtin.RewardActorCodeID, + Head: rewardResult.NewHead, + CallSeqNum: rewardActorIn.CallSeqNum, + Balance: big.Add(rewardActorIn.Balance, rewardResult.Transfer), + } + err = actorsOut.SetActor(builtin.RewardActorAddr, &rewardActorOut) + if err != nil { + return cid.Undef, err + } + // Migrate verified registry vm := migrations[builtin0.VerifiedRegistryActorCodeID].StateMigration.(*verifregMigrator) vm.actorsOut = actorsOut diff --git a/actors/util/math/exp.go b/actors/util/math/exp.go new file mode 100644 index 000000000..215bd0a33 --- /dev/null +++ b/actors/util/math/exp.go @@ -0,0 +1,29 @@ +package math + +import "github.com/filecoin-project/go-state-types/big" + +// ExpBySquaring takes a Q.128 base b and an int64 exponent n and computes n^b +// using the exponentiation by squaring method, returning a Q.128 value. +func ExpBySquaring(base big.Int, n int64) big.Int { + one := big.Lsh(big.NewInt(1), Precision128) + // Base cases + if n == 0 { + return one + } + if n == 1 { + return base + } + + // Recurse + if n < 0 { + inverseBase := big.Div(big.Lsh(one, Precision128), base) // Q.256 / Q.128 => Q.128 + return ExpBySquaring(inverseBase, -n) + } + baseSquared := big.Mul(base, base) // Q.128 * Q.128 => Q.256 + baseSquared = big.Rsh(baseSquared, Precision128) // Q.256 => Q.128 + if n%2 == 0 { + return ExpBySquaring(baseSquared, n/2) + } + result := big.Mul(base, ExpBySquaring(baseSquared, (n-1)/2)) // Q.128 * Q.128 => Q.256 + return big.Rsh(result, Precision128) // Q.256 => Q.128 +} diff --git a/actors/util/math/exp_test.go b/actors/util/math/exp_test.go new file mode 100644 index 000000000..3ee348b70 --- /dev/null +++ b/actors/util/math/exp_test.go @@ -0,0 +1,38 @@ +package math_test + +import ( + "testing" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/v2/actors/util/math" + "github.com/stretchr/testify/assert" +) + +func TestExpBySquaring(t *testing.T) { + one := big.Lsh(big.NewInt(1), math.Precision128) + two := big.Lsh(big.NewInt(2), math.Precision128) + three := big.Lsh(big.NewInt(3), math.Precision128) + five := big.Lsh(big.NewInt(5), math.Precision128) + seven := big.Lsh(big.NewInt(7), math.Precision128) + + assert.Equal(t, one, math.ExpBySquaring(three, int64(0))) + assert.Equal(t, one, math.ExpBySquaring(one, 1)) + assert.Equal(t, three, math.ExpBySquaring(three, 1)) + + assert.Equal(t, + big.Lsh(big.NewInt(70368744177664), math.Precision128), + math.ExpBySquaring(two, 46), + ) + assert.Equal(t, + big.Lsh(big.NewInt(3486784401), math.Precision128), + math.ExpBySquaring(three, 20), + ) + assert.Equal(t, + big.Lsh(big.NewInt(1953125), math.Precision128), + math.ExpBySquaring(five, 9), + ) + assert.Equal(t, + big.Lsh(big.NewInt(117649), math.Precision128), + math.ExpBySquaring(seven, 6), + ) +} diff --git a/actors/builtin/reward/expneg.go b/actors/util/math/expneg.go similarity index 80% rename from actors/builtin/reward/expneg.go rename to actors/util/math/expneg.go index 4285a9476..9b07d9875 100644 --- a/actors/builtin/reward/expneg.go +++ b/actors/util/math/expneg.go @@ -1,9 +1,9 @@ -package reward +package math import ( "math/big" - "github.com/filecoin-project/specs-actors/v2/actors/util/math" + "github.com/filecoin-project/specs-actors/actors/util/math" ) var ( @@ -48,16 +48,16 @@ func init() { expDenoCoef = math.Parse(deno) } -// expneg accepts x in Q.128 format and computes e^-x. +// ExpNeg accepts x in Q.128 format and computes e^-x. // It is most precise within [0, 1.725) range, where error is less than 3.4e-30. // Over the [0, 5) range its error is less than 4.6e-15. // Output is in Q.128 format. -func expneg(x *big.Int) *big.Int { +func ExpNeg(x *big.Int) *big.Int { // exp is approximated by rational function // polynomials of the rational function are evaluated using Horner's method - num := math.Polyval(expNumCoef, x) // Q.128 - deno := math.Polyval(expDenoCoef, x) // Q.128 + num := Polyval(expNumCoef, x) // Q.128 + deno := Polyval(expDenoCoef, x) // Q.128 - num = num.Lsh(num, math.Precision) // Q.256 - return num.Div(num, deno) // Q.256 / Q.128 => Q.128 + num = num.Lsh(num, Precision128) // Q.256 + return num.Div(num, deno) // Q.256 / Q.128 => Q.128 } diff --git a/actors/builtin/reward/expneg_test.go b/actors/util/math/expneg_test.go similarity index 81% rename from actors/builtin/reward/expneg_test.go rename to actors/util/math/expneg_test.go index 33226a5b1..2f6aa40c3 100644 --- a/actors/builtin/reward/expneg_test.go +++ b/actors/util/math/expneg_test.go @@ -1,4 +1,4 @@ -package reward +package math_test import ( "bytes" @@ -15,7 +15,7 @@ var Res big.Word func BenchmarkExpneg(b *testing.B) { x := new(big.Int).SetUint64(14) - x = x.Lsh(x, math.Precision-3) // set x to 1.75 + x = x.Lsh(x, math.Precision128-3) // set x to 1.75 dec := new(big.Int) dec = dec.Div(x, big.NewInt(int64(b.N))) b.ResetTimer() @@ -23,7 +23,7 @@ func BenchmarkExpneg(b *testing.B) { var res big.Word for i := 0; i < b.N; i++ { - r := expneg(x) + r := math.ExpNeg(x) res += r.Bits()[0] x.Sub(x, dec) } @@ -34,7 +34,7 @@ func TestExpFunction(t *testing.T) { const N = 256 step := big.NewInt(5) - step = step.Lsh(step, math.Precision) // Q.128 + step = step.Lsh(step, math.Precision128) // Q.128 step = step.Div(step, big.NewInt(N-1)) x := big.NewInt(0) @@ -42,7 +42,7 @@ func TestExpFunction(t *testing.T) { b.WriteString("x, y\n") for i := 0; i < N; i++ { - y := expneg(x) + y := math.ExpNeg(x) fmt.Fprintf(b, "%s,%s\n", x, y) x = x.Add(x, step) } diff --git a/actors/util/math/ln.go b/actors/util/math/ln.go new file mode 100644 index 000000000..918674b36 --- /dev/null +++ b/actors/util/math/ln.go @@ -0,0 +1,79 @@ +package math + +import ( + gbig "math/big" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/actors/util/math" +) + +var ( + // Coefficients in Q.128 format + lnNumCoef []*gbig.Int + lnDenomCoef []*gbig.Int + ln2 big.Int +) + +func init() { + // ln approximation coefficients + // parameters are in integer format, + // coefficients are *2^-128 of that + // so we can just load them if we treat them as Q.128 + num := []string{ + "261417938209272870992496419296200268025", + "7266615505142943436908456158054846846897", + "32458783941900493142649393804518050491988", + "17078670566130897220338060387082146864806", + "-35150353308172866634071793531642638290419", + "-20351202052858059355702509232125230498980", + "-1563932590352680681114104005183375350999", + } + lnNumCoef = math.Parse(num) + + denom := []string{ + "49928077726659937662124949977867279384", + "2508163877009111928787629628566491583994", + "21757751789594546643737445330202599887121", + "53400635271583923415775576342898617051826", + "41248834748603606604000911015235164348839", + "9015227820322455780436733526367238305537", + "340282366920938463463374607431768211456", + } + lnDenomCoef = math.Parse(denom) + + constStrs := []string{ + "235865763225513294137944142764154484399", // ln(2) + } + constBigs := Parse(constStrs) + ln2 = big.NewFromGo(constBigs[0]) +} + +// The natural log of Q.128 x. +func Ln(z big.Int) big.Int { + // bitlen - 1 - precision + k := int64(z.BitLen()) - 1 - Precision128 // Q.0 + x := big.Zero() // nolint:ineffassign + + if k > 0 { + x = big.Rsh(z, uint(k)) // Q.128 + } else { + x = big.Lsh(z, uint(-k)) // Q.128 + } + + // ln(z) = ln(x * 2^k) = ln(x) + k * ln2 + lnz := big.Mul(big.NewInt(k), ln2) // Q.0 * Q.128 => Q.128 + return big.Sum(lnz, lnBetweenOneAndTwo(x)) // Q.128 +} + +// The natural log of x, specified in Q.128 format +// Should only use with 1 <= x <= 2 +// Output is in Q.128 format. +func lnBetweenOneAndTwo(x big.Int) big.Int { + // ln is approximated by rational function + // polynomials of the rational function are evaluated using Horner's method + num := Polyval(lnNumCoef, x.Int) // Q.128 + denom := Polyval(lnDenomCoef, x.Int) // Q.128 + + num = num.Lsh(num, Precision128) // Q.128 => Q.256 + return big.NewFromGo(num.Div(num, denom)) // Q.256 / Q.128 => Q.128 +} diff --git a/actors/util/math/ln_test.go b/actors/util/math/ln_test.go new file mode 100644 index 000000000..37afb28ec --- /dev/null +++ b/actors/util/math/ln_test.go @@ -0,0 +1,39 @@ +package math_test + +import ( + "fmt" + "testing" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/v2/actors/util/math" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNaturalLog(t *testing.T) { + lnInputs := math.Parse([]string{ + "340282366920938463463374607431768211456", // Q.128 format of 1 + "924990000000000000000000000000000000000", // Q.128 format of e (rounded up in 5th decimal place to handle truncation) + "34028236692093846346337460743176821145600000000000000000000", // Q.128 format of 100e18 + "6805647338418769269267492148635364229120000000000000000000000", // Q.128 format of 2e22 + "204169000000000000000000000000000000", // Q.128 format of 0.0006 + "34028236692093846346337460743", // Q.128 format of 1e-10 + }) + + expectedLnOutputs := math.Parse([]string{ + "0", // Q.128 format of 0 = ln(1) + "340282366920938463463374607431768211456", // Q.128 format of 1 = ln(e) + "15670582109617661336106769654068947397831", // Q.128 format of 46.051... = ln(100e18) + "17473506083804940763855390762239996622013", // Q.128 format of 51.35... = ln(2e22) + "-2524410000000000000000000000000000000000", // Q.128 format of -7.41.. = ln(0.0006) + "-7835291054808830668053384827034473698915", // Q.128 format of -23.02.. = ln(1e-10) + }) + fmt.Printf("%v %v\n", lnInputs, expectedLnOutputs) + require.Equal(t, len(lnInputs), len(expectedLnOutputs)) + for i := 0; i < len(lnInputs); i++ { + z := big.NewFromGo(lnInputs[i]) + lnOfZ := math.Ln(z) + expectedZ := big.NewFromGo(expectedLnOutputs[i]) + assert.Equal(t, big.Rsh(expectedZ, math.Precision128), big.Rsh(lnOfZ, math.Precision128), "failed ln of %v", z) + } +} diff --git a/actors/util/math/polyval.go b/actors/util/math/polyval.go index 25fff1e87..81412d6a4 100644 --- a/actors/util/math/polyval.go +++ b/actors/util/math/polyval.go @@ -3,7 +3,7 @@ package math import "math/big" // note: all coefficients for which Polyval is used would need to be updated if this precision changes -const Precision = 128 +const Precision128 = 128 // polyval evaluates a polynomial given by coefficients `p` in Q.128 format // at point `x` in Q.128 format. Output is in Q.128. @@ -13,8 +13,8 @@ func Polyval(p []*big.Int, x *big.Int) *big.Int { res := new(big.Int).Set(p[0]) // Q.128 tmp := new(big.Int) // big.Int.Mul doesn't like when input is reused as output for _, c := range p[1:] { - tmp = tmp.Mul(res, x) // Q.128 * Q.128 => Q.256 - res = res.Rsh(tmp, Precision) // Q.256 >> 128 => Q.128 + tmp = tmp.Mul(res, x) // Q.128 * Q.128 => Q.256 + res = res.Rsh(tmp, Precision128) // Q.256 >> 128 => Q.128 res = res.Add(res, c) } diff --git a/actors/builtin/reward/testdata/TestExpFunction.golden b/actors/util/math/testdata/TestExpFunction.golden similarity index 100% rename from actors/builtin/reward/testdata/TestExpFunction.golden rename to actors/util/math/testdata/TestExpFunction.golden diff --git a/actors/util/smoothing/alpha_beta_filter.go b/actors/util/smoothing/alpha_beta_filter.go index 2b7a94f77..2c63bf4d0 100644 --- a/actors/util/smoothing/alpha_beta_filter.go +++ b/actors/util/smoothing/alpha_beta_filter.go @@ -1,8 +1,6 @@ package smoothing import ( - gbig "math/big" - "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -10,11 +8,6 @@ import ( ) var ( - // Coefficents in Q.128 format - lnNumCoef []*gbig.Int - lnDenomCoef []*gbig.Int - ln2 big.Int - defaultInitialPosition big.Int defaultInitialVelocity big.Int @@ -27,45 +20,18 @@ func init() { defaultInitialPosition = big.Zero() defaultInitialVelocity = big.Zero() - // ln approximation coefficients - // parameters are in integer format, - // coefficients are *2^-128 of that - // so we can just load them if we treat them as Q.128 - num := []string{ - "261417938209272870992496419296200268025", - "7266615505142943436908456158054846846897", - "32458783941900493142649393804518050491988", - "17078670566130897220338060387082146864806", - "-35150353308172866634071793531642638290419", - "-20351202052858059355702509232125230498980", - "-1563932590352680681114104005183375350999", - } - lnNumCoef = math.Parse(num) - - denom := []string{ - "49928077726659937662124949977867279384", - "2508163877009111928787629628566491583994", - "21757751789594546643737445330202599887121", - "53400635271583923415775576342898617051826", - "41248834748603606604000911015235164348839", - "9015227820322455780436733526367238305537", - "340282366920938463463374607431768211456", - } - lnDenomCoef = math.Parse(denom) - // Alpha Beta Filter constants constStrs := []string{ - "314760000000000000000000000000000000", // DefaultAlpha - "96640100000000000000000000000000", // DefaultBeta - "302231454903657293676544", // Epsilon - "235865763225513294137944142764154484399", // ln(2) + "314760000000000000000000000000000000", // DefaultAlpha + "96640100000000000000000000000000", // DefaultBeta + "302231454903657293676544", // Epsilon + } constBigs := math.Parse(constStrs) - _ = math.Parse(constStrs) DefaultAlpha = big.NewFromGo(constBigs[0]) DefaultBeta = big.NewFromGo(constBigs[1]) ExtrapolatedCumSumRatioEpsilon = big.NewFromGo(constBigs[2]) - ln2 = big.NewFromGo(constBigs[3]) + } // Alpha Beta Filter "position" (value) and "velocity" (rate of change of value) estimates @@ -77,7 +43,7 @@ type FilterEstimate struct { // Returns the Q.0 position estimate of the filter func (fe *FilterEstimate) Estimate() big.Int { - return big.Rsh(fe.PositionEstimate, math.Precision) // Q.128 => Q.0 + return big.Rsh(fe.PositionEstimate, math.Precision128) // Q.128 => Q.0 } func DefaultInitialEstimate() FilterEstimate { @@ -90,8 +56,8 @@ func DefaultInitialEstimate() FilterEstimate { // Create a new filter estimate given two Q.0 format ints. func NewEstimate(position, velocity big.Int) FilterEstimate { return FilterEstimate{ - PositionEstimate: big.Lsh(position, math.Precision), // Q.0 => Q.128 - VelocityEstimate: big.Lsh(velocity, math.Precision), // Q.0 => Q.128 + PositionEstimate: big.Lsh(position, math.Precision128), // Q.0 => Q.128 + VelocityEstimate: big.Lsh(velocity, math.Precision128), // Q.0 => Q.128 } } @@ -110,15 +76,15 @@ func LoadFilter(prevEstimate FilterEstimate, alpha, beta big.Int) *AlphaBetaFilt } func (f *AlphaBetaFilter) NextEstimate(observation big.Int, epochDelta abi.ChainEpoch) FilterEstimate { - deltaT := big.Lsh(big.NewInt(int64(epochDelta)), math.Precision) // Q.0 => Q.128 - deltaX := big.Mul(deltaT, f.prevEstimate.VelocityEstimate) // Q.128 * Q.128 => Q.256 - deltaX = big.Rsh(deltaX, math.Precision) // Q.256 => Q.128 + deltaT := big.Lsh(big.NewInt(int64(epochDelta)), math.Precision128) // Q.0 => Q.128 + deltaX := big.Mul(deltaT, f.prevEstimate.VelocityEstimate) // Q.128 * Q.128 => Q.256 + deltaX = big.Rsh(deltaX, math.Precision128) // Q.256 => Q.128 position := big.Sum(f.prevEstimate.PositionEstimate, deltaX) - observation = big.Lsh(observation, math.Precision) // Q.0 => Q.128 + observation = big.Lsh(observation, math.Precision128) // Q.0 => Q.128 residual := big.Sub(observation, position) - revisionX := big.Mul(f.alpha, residual) // Q.128 * Q.128 => Q.256 - revisionX = big.Rsh(revisionX, math.Precision) // Q.256 => Q.128 + revisionX := big.Mul(f.alpha, residual) // Q.128 * Q.128 => Q.256 + revisionX = big.Rsh(revisionX, math.Precision128) // Q.256 => Q.128 position = big.Sum(position, revisionX) revisionV := big.Mul(f.beta, residual) // Q.128 * Q.128 => Q.256 @@ -134,39 +100,39 @@ func (f *AlphaBetaFilter) NextEstimate(observation big.Int, epochDelta abi.Chain // Extrapolate the CumSumRatio given two filters. // Output is in Q.128 format func ExtrapolatedCumSumOfRatio(delta abi.ChainEpoch, relativeStart abi.ChainEpoch, estimateNum, estimateDenom FilterEstimate) big.Int { - deltaT := big.Lsh(big.NewInt(int64(delta)), math.Precision) // Q.0 => Q.128 - t0 := big.Lsh(big.NewInt(int64(relativeStart)), math.Precision) // Q.0 => Q.128 + deltaT := big.Lsh(big.NewInt(int64(delta)), math.Precision128) // Q.0 => Q.128 + t0 := big.Lsh(big.NewInt(int64(relativeStart)), math.Precision128) // Q.0 => Q.128 // Renaming for ease of following spec and clarity position1 := estimateNum.PositionEstimate position2 := estimateDenom.PositionEstimate velocity1 := estimateNum.VelocityEstimate velocity2 := estimateDenom.VelocityEstimate - squaredVelocity2 := big.Mul(velocity2, velocity2) // Q.128 * Q.128 => Q.256 - squaredVelocity2 = big.Rsh(squaredVelocity2, math.Precision) // Q.256 => Q.128 + squaredVelocity2 := big.Mul(velocity2, velocity2) // Q.128 * Q.128 => Q.256 + squaredVelocity2 = big.Rsh(squaredVelocity2, math.Precision128) // Q.256 => Q.128 if squaredVelocity2.GreaterThan(ExtrapolatedCumSumRatioEpsilon) { - x2a := big.Mul(t0, velocity2) // Q.128 * Q.128 => Q.256 - x2a = big.Rsh(x2a, math.Precision) // Q.256 => Q.128 + x2a := big.Mul(t0, velocity2) // Q.128 * Q.128 => Q.256 + x2a = big.Rsh(x2a, math.Precision128) // Q.256 => Q.128 x2a = big.Sum(position2, x2a) - x2b := big.Mul(deltaT, velocity2) // Q.128 * Q.128 => Q.256 - x2b = big.Rsh(x2b, math.Precision) // Q.256 => Q.128 + x2b := big.Mul(deltaT, velocity2) // Q.128 * Q.128 => Q.256 + x2b = big.Rsh(x2b, math.Precision128) // Q.256 => Q.128 x2b = big.Sum(x2a, x2b) - x2a = Ln(x2a) // Q.128 - x2b = Ln(x2b) // Q.128 + x2a = math.Ln(x2a) // Q.128 + x2b = math.Ln(x2b) // Q.128 m1 := big.Sub(x2b, x2a) m1 = big.Mul(velocity2, big.Mul(position1, m1)) // Q.128 * Q.128 * Q.128 => Q.384 - m1 = big.Rsh(m1, math.Precision) //Q.384 => Q.256 + m1 = big.Rsh(m1, math.Precision128) //Q.384 => Q.256 m2L := big.Sub(x2a, x2b) m2L = big.Mul(position2, m2L) // Q.128 * Q.128 => Q.256 m2R := big.Mul(velocity2, deltaT) // Q.128 * Q.128 => Q.256 m2 := big.Sum(m2L, m2R) - m2 = big.Mul(velocity1, m2) // Q.256 => Q.384 - m2 = big.Rsh(m2, math.Precision) //Q.384 => Q.256 + m2 = big.Mul(velocity1, m2) // Q.256 => Q.384 + m2 = big.Rsh(m2, math.Precision128) //Q.384 => Q.256 return big.Div(big.Sum(m1, m2), squaredVelocity2) // Q.256 / Q.128 => Q.128 @@ -174,7 +140,7 @@ func ExtrapolatedCumSumOfRatio(delta abi.ChainEpoch, relativeStart abi.ChainEpoc halfDeltaT := big.Rsh(deltaT, 1) // Q.128 / Q.0 => Q.128 x1m := big.Mul(velocity1, big.Sum(t0, halfDeltaT)) // Q.128 * Q.128 => Q.256 - x1m = big.Rsh(x1m, math.Precision) // Q.256 => Q.128 + x1m = big.Rsh(x1m, math.Precision128) // Q.256 => Q.128 x1m = big.Add(position1, x1m) cumsumRatio := big.Mul(x1m, deltaT) // Q.128 * Q.128 => Q.256 @@ -183,44 +149,14 @@ func ExtrapolatedCumSumOfRatio(delta abi.ChainEpoch, relativeStart abi.ChainEpoc } -// The natural log of Q.128 x. -func Ln(z big.Int) big.Int { - // bitlen - 1 - precision - k := int64(z.BitLen()) - 1 - math.Precision // Q.0 - x := big.Zero() // nolint:ineffassign - - if k > 0 { - x = big.Rsh(z, uint(k)) // Q.128 - } else { - x = big.Lsh(z, uint(-k)) // Q.128 - } - - // ln(z) = ln(x * 2^k) = ln(x) + k * ln2 - lnz := big.Mul(big.NewInt(k), ln2) // Q.0 * Q.128 => Q.128 - return big.Sum(lnz, lnBetweenOneAndTwo(x)) // Q.128 -} - -// The natural log of x, specified in Q.128 format -// Should only use with 1 <= x <= 2 -// Output is in Q.128 format. -func lnBetweenOneAndTwo(x big.Int) big.Int { - // ln is approximated by rational function - // polynomials of the rational function are evaluated using Horner's method - num := math.Polyval(lnNumCoef, x.Int) // Q.128 - denom := math.Polyval(lnDenomCoef, x.Int) // Q.128 - - num = num.Lsh(num, math.Precision) // Q.128 => Q.256 - return big.NewFromGo(num.Div(num, denom)) // Q.256 / Q.128 => Q.128 -} - // Extrapolate filter "position" delta epochs in the future. // Note this is currently only used in testing. // Output is Q.256 format for use in numerator of ratio in test caller func (fe *FilterEstimate) Extrapolate(delta abi.ChainEpoch) big.Int { - deltaT := big.NewInt(int64(delta)) // Q.0 - deltaT = big.Lsh(deltaT, math.Precision) // Q.0 => Q.128 - extrapolation := big.Mul(fe.VelocityEstimate, deltaT) // Q.128 * Q.128 => Q.256 - position := big.Lsh(fe.PositionEstimate, math.Precision) // Q.128 => Q.256 + deltaT := big.NewInt(int64(delta)) // Q.0 + deltaT = big.Lsh(deltaT, math.Precision128) // Q.0 => Q.128 + extrapolation := big.Mul(fe.VelocityEstimate, deltaT) // Q.128 * Q.128 => Q.256 + position := big.Lsh(fe.PositionEstimate, math.Precision128) // Q.128 => Q.256 extrapolation = big.Sum(position, extrapolation) return extrapolation // Q.256 } diff --git a/actors/util/smoothing/alpha_beta_filter_test.go b/actors/util/smoothing/alpha_beta_filter_test.go index b4d5f197e..3ccd1f6a4 100644 --- a/actors/util/smoothing/alpha_beta_filter_test.go +++ b/actors/util/smoothing/alpha_beta_filter_test.go @@ -1,13 +1,11 @@ package smoothing_test import ( - "fmt" "testing" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/filecoin-project/specs-actors/v2/actors/builtin" "github.com/filecoin-project/specs-actors/v2/actors/util/math" @@ -21,12 +19,12 @@ func TestCumSumRatioProjection(t *testing.T) { denomEstimate := smoothing.TestingConstantEstimate(big.NewInt(1)) // 4e6/1 over 1000 epochs should give us 4e9 csr := smoothing.ExtrapolatedCumSumOfRatio(abi.ChainEpoch(1000), abi.ChainEpoch(0), numEstimate, denomEstimate) - csr = big.Rsh(csr, math.Precision) + csr = big.Rsh(csr, math.Precision128) assert.Equal(t, big.NewInt(4e9), csr) // if we change t0 nothing should change because velocity is 0 csr2 := smoothing.ExtrapolatedCumSumOfRatio(abi.ChainEpoch(1000), abi.ChainEpoch(1e15), numEstimate, denomEstimate) - csr2 = big.Rsh(csr2, math.Precision) + csr2 = big.Rsh(csr2, math.Precision128) assert.Equal(t, csr, csr2) // 1e12 / 200e12 for 100 epochs should give ratio of 1/2 @@ -34,22 +32,22 @@ func TestCumSumRatioProjection(t *testing.T) { denomEstimate = smoothing.TestingConstantEstimate(big.NewInt(200e12)) csrFrac := smoothing.ExtrapolatedCumSumOfRatio(abi.ChainEpoch(100), abi.ChainEpoch(0), numEstimate, denomEstimate) // If we didn't return Q.128 we'd just get zero - assert.Equal(t, big.Zero(), big.Rsh(csrFrac, math.Precision)) + assert.Equal(t, big.Zero(), big.Rsh(csrFrac, math.Precision128)) // multiply by 10k and we'll get 5k // note: this is a bit sensative to input, lots of numbers approach from below // (...99999) and so truncating division takes us off by one - product := big.Mul(csrFrac, big.Lsh(big.NewInt(10000), math.Precision)) // Q.256 - assert.Equal(t, big.NewInt(5000), big.Rsh(product, 2*math.Precision)) + product := big.Mul(csrFrac, big.Lsh(big.NewInt(10000), math.Precision128)) // Q.256 + assert.Equal(t, big.NewInt(5000), big.Rsh(product, 2*math.Precision128)) }) // Q.128 cumsum of ratio using the trapezoid rule iterativeCumSumOfRatio := func(num, denom smoothing.FilterEstimate, t0, delta abi.ChainEpoch) big.Int { ratio := big.Zero() // Q.128 for i := abi.ChainEpoch(0); i < delta; i++ { - numEpsilon := num.Extrapolate(t0 + i) // Q.256 - denomEpsilon := denom.Extrapolate(t0 + i) // Q.256 - denomEpsilon = big.Rsh(denomEpsilon, math.Precision) // Q.256 => Q.128 - epsilon := big.Div(numEpsilon, denomEpsilon) // Q.256 / Q.128 => Q.128 + numEpsilon := num.Extrapolate(t0 + i) // Q.256 + denomEpsilon := denom.Extrapolate(t0 + i) // Q.256 + denomEpsilon = big.Rsh(denomEpsilon, math.Precision128) // Q.256 => Q.128 + epsilon := big.Div(numEpsilon, denomEpsilon) // Q.256 / Q.128 => Q.128 if i != abi.ChainEpoch(0) && i != delta-1 { epsilon = big.Mul(big.NewInt(2), epsilon) // Q.128 * Q.0 => Q.128 } @@ -153,47 +151,19 @@ func TestCumSumRatioProjection(t *testing.T) { } -func TestNaturalLog(t *testing.T) { - lnInputs := math.Parse([]string{ - "340282366920938463463374607431768211456", // Q.128 format of 1 - "924990000000000000000000000000000000000", // Q.128 format of e (rounded up in 5th decimal place to handle truncation) - "34028236692093846346337460743176821145600000000000000000000", // Q.128 format of 100e18 - "6805647338418769269267492148635364229120000000000000000000000", // Q.128 format of 2e22 - "204169000000000000000000000000000000", // Q.128 format of 0.0006 - "34028236692093846346337460743", // Q.128 format of 1e-10 - }) - - expectedLnOutputs := math.Parse([]string{ - "0", // Q.128 format of 0 = ln(1) - "340282366920938463463374607431768211456", // Q.128 format of 1 = ln(e) - "15670582109617661336106769654068947397831", // Q.128 format of 46.051... = ln(100e18) - "17473506083804940763855390762239996622013", // Q.128 format of 51.35... = ln(2e22) - "-2524410000000000000000000000000000000000", // Q.128 format of -7.41.. = ln(0.0006) - "-7835291054808830668053384827034473698915", // Q.128 format of -23.02.. = ln(1e-10) - }) - fmt.Printf("%v %v\n", lnInputs, expectedLnOutputs) - require.Equal(t, len(lnInputs), len(expectedLnOutputs)) - for i := 0; i < len(lnInputs); i++ { - z := big.NewFromGo(lnInputs[i]) - lnOfZ := smoothing.Ln(z) - expectedZ := big.NewFromGo(expectedLnOutputs[i]) - assert.Equal(t, big.Rsh(expectedZ, math.Precision), big.Rsh(lnOfZ, math.Precision), "failed ln of %v", z) - } -} - // Millionths of difference between val1 and val2 // (val1 - val2) / val1 * 1e6 // all inputs Q.128, output Q.0 func perMillionError(val1, val2 big.Int) big.Int { diff := big.Sub(val1, val2) - diff = big.Lsh(diff, math.Precision) // Q.128 => Q.256 - perMillion := big.Div(diff, val1) // Q.256 / Q.128 => Q.128 - million := big.Lsh(big.NewInt(1e6), math.Precision) // Q.0 => Q.128 + diff = big.Lsh(diff, math.Precision128) // Q.128 => Q.256 + perMillion := big.Div(diff, val1) // Q.256 / Q.128 => Q.128 + million := big.Lsh(big.NewInt(1e6), math.Precision128) // Q.0 => Q.128 perMillion = big.Mul(perMillion, million) // Q.128 * Q.128 => Q.256 if perMillion.LessThan(big.Zero()) { perMillion = perMillion.Neg() } - return big.Rsh(perMillion, 2*math.Precision) + return big.Rsh(perMillion, 2*math.Precision128) }