Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Foundation subsidy to be waived #237

Merged
merged 4 commits into from
Dec 2, 2024
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
80 changes: 42 additions & 38 deletions consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ func (n *Network) GenesisState() State {
ChildTarget: n.InitialTarget,
SiafundPool: types.ZeroCurrency,

OakTime: 0,
OakTarget: intToTarget(maxTarget),
FoundationPrimaryAddress: n.HardforkFoundation.PrimaryAddress,
FoundationFailsafeAddress: n.HardforkFoundation.FailsafeAddress,
TotalWork: Work{invTarget(intToTarget(maxTarget))},
Difficulty: Work{invTarget(n.InitialTarget)},
OakWork: Work{invTarget(intToTarget(maxTarget))},
OakTime: 0,
OakTarget: intToTarget(maxTarget),
FoundationSubsidyAddress: n.HardforkFoundation.PrimaryAddress,
FoundationManagementAddress: n.HardforkFoundation.FailsafeAddress,
TotalWork: Work{invTarget(intToTarget(maxTarget))},
Difficulty: Work{invTarget(n.InitialTarget)},
OakWork: Work{invTarget(intToTarget(maxTarget))},
}
}

Expand All @@ -121,8 +121,8 @@ type State struct {
OakTime time.Duration `json:"oakTime"`
OakTarget types.BlockID `json:"oakTarget"`
// Foundation hardfork state
FoundationPrimaryAddress types.Address `json:"foundationPrimaryAddress"`
FoundationFailsafeAddress types.Address `json:"foundationFailsafeAddress"`
FoundationSubsidyAddress types.Address `json:"foundationSubsidyAddress"`
FoundationManagementAddress types.Address `json:"foundationManagementAddress"`
// v2 hardfork state
TotalWork Work `json:"totalWork"`
Difficulty Work `json:"difficulty"`
Expand All @@ -143,8 +143,8 @@ func (s State) EncodeTo(e *types.Encoder) {

e.WriteUint64(uint64(s.OakTime))
s.OakTarget.EncodeTo(e)
s.FoundationPrimaryAddress.EncodeTo(e)
s.FoundationFailsafeAddress.EncodeTo(e)
s.FoundationSubsidyAddress.EncodeTo(e)
s.FoundationManagementAddress.EncodeTo(e)
s.TotalWork.EncodeTo(e)
s.Difficulty.EncodeTo(e)
s.OakWork.EncodeTo(e)
Expand All @@ -164,8 +164,8 @@ func (s *State) DecodeFrom(d *types.Decoder) {

s.OakTime = time.Duration(d.ReadUint64())
s.OakTarget.DecodeFrom(d)
s.FoundationPrimaryAddress.DecodeFrom(d)
s.FoundationFailsafeAddress.DecodeFrom(d)
s.FoundationSubsidyAddress.DecodeFrom(d)
s.FoundationManagementAddress.DecodeFrom(d)
s.TotalWork.DecodeFrom(d)
s.Difficulty.DecodeFrom(d)
s.OakWork.DecodeFrom(d)
Expand Down Expand Up @@ -257,22 +257,23 @@ func (s State) AncestorDepth() uint64 {
}

// FoundationSubsidy returns the Foundation subsidy output for the child block.
// If no subsidy is due, the returned output has a value of zero.
func (s State) FoundationSubsidy() (sco types.SiacoinOutput, exists bool) {
sco.Address = s.FoundationPrimaryAddress

if s.FoundationSubsidyAddress == types.VoidAddress {
return types.SiacoinOutput{}, false
}
sco.Address = s.FoundationSubsidyAddress
subsidyPerBlock := types.Siacoins(30000)
blocksPerYear := uint64(365 * 24 * time.Hour / s.BlockInterval())
blocksPerMonth := blocksPerYear / 12
hardforkHeight := s.Network.HardforkFoundation.Height
if s.childHeight() < hardforkHeight || (s.childHeight()-hardforkHeight)%blocksPerMonth != 0 {
sco.Value = types.ZeroCurrency
return types.SiacoinOutput{}, false
} else if s.childHeight() == hardforkHeight {
sco.Value = subsidyPerBlock.Mul64(blocksPerYear)
} else {
sco.Value = subsidyPerBlock.Mul64(blocksPerMonth)
}
return sco, !sco.Value.IsZero()
return sco, true
}

// NonceFactor is the factor by which all block nonces must be divisible.
Expand Down Expand Up @@ -342,6 +343,9 @@ func (s State) V2TransactionWeight(txn types.V2Transaction) uint64 {
a.EncodeTo(e)
}
e.Write(txn.ArbitraryData)
if txn.NewFoundationAddress != nil {
txn.NewFoundationAddress.EncodeTo(e)
}
e.Flush()
return uint64(wc.n)
}
Expand Down Expand Up @@ -588,16 +592,16 @@ func (s State) AttestationSigHash(a types.Attestation) types.Hash256 {

// A MidState represents the state of the chain within a block.
type MidState struct {
base State
created map[types.ElementID]int // indices into element slices
spends map[types.ElementID]types.TransactionID
revs map[types.FileContractID]*types.FileContractElement
res map[types.FileContractID]bool
v2revs map[types.FileContractID]*types.V2FileContractElement
v2res map[types.FileContractID]types.V2FileContractResolutionType
siafundPool types.Currency
foundationPrimary types.Address
foundationFailsafe types.Address
base State
created map[types.ElementID]int // indices into element slices
spends map[types.ElementID]types.TransactionID
revs map[types.FileContractID]*types.FileContractElement
res map[types.FileContractID]bool
v2revs map[types.FileContractID]*types.V2FileContractElement
v2res map[types.FileContractID]types.V2FileContractResolutionType
siafundPool types.Currency
foundationSubsidy types.Address
foundationManagement types.Address

// elements created/updated by block
sces []types.SiacoinElement
Expand Down Expand Up @@ -650,16 +654,16 @@ func (ms *MidState) isCreated(id types.ElementID) bool {
// NewMidState constructs a MidState initialized to the provided base state.
func NewMidState(s State) *MidState {
return &MidState{
base: s,
created: make(map[types.ElementID]int),
spends: make(map[types.ElementID]types.TransactionID),
revs: make(map[types.FileContractID]*types.FileContractElement),
res: make(map[types.FileContractID]bool),
v2revs: make(map[types.FileContractID]*types.V2FileContractElement),
v2res: make(map[types.FileContractID]types.V2FileContractResolutionType),
siafundPool: s.SiafundPool,
foundationPrimary: s.FoundationPrimaryAddress,
foundationFailsafe: s.FoundationFailsafeAddress,
base: s,
created: make(map[types.ElementID]int),
spends: make(map[types.ElementID]types.TransactionID),
revs: make(map[types.FileContractID]*types.FileContractElement),
res: make(map[types.FileContractID]bool),
v2revs: make(map[types.FileContractID]*types.V2FileContractElement),
v2res: make(map[types.FileContractID]types.V2FileContractResolutionType),
siafundPool: s.SiafundPool,
foundationSubsidy: s.FoundationSubsidyAddress,
foundationManagement: s.FoundationManagementAddress,
}
}

Expand Down
17 changes: 11 additions & 6 deletions consensus/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,8 @@ func (ms *MidState) ApplyTransaction(txn types.Transaction, ts V1TransactionSupp
if bytes.HasPrefix(arb, types.SpecifierFoundation[:]) {
var update types.FoundationAddressUpdate
update.DecodeFrom(types.NewBufDecoder(arb[len(types.SpecifierFoundation):]))
ms.foundationPrimary = update.NewPrimary
ms.foundationFailsafe = update.NewFailsafe
ms.foundationSubsidy = update.NewPrimary
ms.foundationManagement = update.NewFailsafe
}
}
}
Expand Down Expand Up @@ -568,8 +568,13 @@ func (ms *MidState) ApplyV2Transaction(txn types.V2Transaction) {
})
}
if txn.NewFoundationAddress != nil {
ms.foundationPrimary = *txn.NewFoundationAddress
ms.foundationFailsafe = *txn.NewFoundationAddress
// The subsidy may be waived by sending it to the void address. In this
// case, the management address is not updated (as this would
// permanently disable the subsidy).
ms.foundationSubsidy = *txn.NewFoundationAddress
if *txn.NewFoundationAddress != types.VoidAddress {
ms.foundationManagement = *txn.NewFoundationAddress
}
}
}

Expand Down Expand Up @@ -738,8 +743,8 @@ func ApplyBlock(s State, b types.Block, bs V1BlockSupplement, targetTimestamp ti
ms.ApplyBlock(b, bs)
s.SiafundPool = ms.siafundPool
s.Attestations += uint64(len(ms.aes))
s.FoundationPrimaryAddress = ms.foundationPrimary
s.FoundationFailsafeAddress = ms.foundationFailsafe
s.FoundationSubsidyAddress = ms.foundationSubsidy
s.FoundationManagementAddress = ms.foundationManagement

// compute updated and added elements
var updated, added []elementLeaf
Expand Down
120 changes: 120 additions & 0 deletions consensus/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1296,3 +1296,123 @@ func TestApplyRevertBlockV2(t *testing.T) {
checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs)
}
}

func TestFoundationSubsidy(t *testing.T) {
key := types.GeneratePrivateKey()
addr := types.StandardAddress(key.PublicKey())
n, genesisBlock := testnet()
n.HardforkFoundation.Height = 1
n.HardforkFoundation.PrimaryAddress = addr
n.HardforkFoundation.FailsafeAddress = addr
n.HardforkV2.AllowHeight = 1
n.HardforkV2.RequireHeight = 1
n.BlockInterval = 10 * time.Hour // subsidies every 10 blocks
subsidyInterval := uint64(365 * 24 * time.Hour / n.BlockInterval / 12)
genesisBlock.Transactions = []types.Transaction{{
SiacoinOutputs: []types.SiacoinOutput{{
Address: addr,
Value: types.Siacoins(1), // funds for changing address later
}},
}}
scoid := genesisBlock.Transactions[0].SiacoinOutputID(0)

db, cs := newConsensusDB(n, genesisBlock)
mineBlock := func(txns []types.V2Transaction) (subsidy types.SiacoinElement, exists bool) {
b := types.Block{
ParentID: cs.Index.ID,
Timestamp: types.CurrentTimestamp(),
MinerPayouts: []types.SiacoinOutput{{Address: types.VoidAddress, Value: cs.BlockReward()}},
V2: &types.V2BlockData{
Height: cs.Index.Height + 1,
Commitment: cs.Commitment(cs.TransactionsCommitment(nil, txns), types.VoidAddress),
Transactions: txns,
},
}
bs := db.supplementTipBlock(b)
findBlockNonce(cs, &b)
if err := ValidateBlock(cs, b, bs); err != nil {
t.Fatal(err)
return
}
var au ApplyUpdate
cs, au = ApplyBlock(cs, b, bs, db.ancestorTimestamp(b.ParentID))
db.applyBlock(au)
au.ForEachSiacoinElement(func(sce types.SiacoinElement, created, _ bool) {
if created && sce.SiacoinOutput.Address == addr {
subsidy = sce
exists = true
}
})
return
}

// receive initial subsidy
initialSubsidy, ok := mineBlock(nil)
if !ok {
t.Fatal("expected subsidy")
}

// mine until we receive a normal subsidy
for range subsidyInterval - 1 {
if _, ok := mineBlock(nil); ok {
t.Fatal("unexpected subsidy")
}
}
subsidy, ok := mineBlock(nil)
if !ok {
t.Fatal("expected subsidy")
} else if subsidy.SiacoinOutput.Value != initialSubsidy.SiacoinOutput.Value.Div64(12) {
t.Fatal("expected subsidy to be 1/12 of initial subsidy")
}
// disable subsidy
txn := types.V2Transaction{
SiacoinInputs: []types.V2SiacoinInput{{
Parent: db.sces[scoid],
SatisfiedPolicy: types.SatisfiedPolicy{
Policy: types.PolicyPublicKey(key.PublicKey()),
},
}},
SiacoinOutputs: []types.SiacoinOutput{{
Address: addr,
Value: db.sces[scoid].SiacoinOutput.Value,
}},
NewFoundationAddress: &types.VoidAddress,
}
txn.SiacoinInputs[0].SatisfiedPolicy.Signatures = []types.Signature{key.SignHash(cs.InputSigHash(txn))}
scoid = txn.SiacoinOutputID(txn.ID(), 0)
mineBlock([]types.V2Transaction{txn})

// mine until we would receive another subsidy
for range subsidyInterval {
if _, ok := mineBlock(nil); ok {
t.Fatal("unexpected subsidy")
}
}

// re-enable subsidy
txn = types.V2Transaction{
SiacoinInputs: []types.V2SiacoinInput{{
Parent: db.sces[scoid],
SatisfiedPolicy: types.SatisfiedPolicy{
Policy: types.PolicyPublicKey(key.PublicKey()),
},
}},
SiacoinOutputs: []types.SiacoinOutput{{
Address: addr,
Value: db.sces[scoid].SiacoinOutput.Value,
}},
NewFoundationAddress: &addr,
}
txn.SiacoinInputs[0].SatisfiedPolicy.Signatures = []types.Signature{key.SignHash(cs.InputSigHash(txn))}
mineBlock([]types.V2Transaction{txn})

// mine until we would receive another subsidy
for range subsidyInterval - 3 {
if _, ok := mineBlock(nil); ok {
t.Fatal("unexpected subsidy")
}
}
if _, ok := mineBlock(nil); !ok {
t.Fatal("expected subsidy")
}
}
4 changes: 2 additions & 2 deletions consensus/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ func validateArbitraryData(ms *MidState, txn types.Transaction) error {
// check that the transaction is signed by a current key
var signed bool
for _, sci := range txn.SiacoinInputs {
if uh := sci.UnlockConditions.UnlockHash(); uh != ms.base.FoundationPrimaryAddress && uh != ms.base.FoundationFailsafeAddress {
if uh := sci.UnlockConditions.UnlockHash(); uh != ms.base.FoundationSubsidyAddress && uh != ms.base.FoundationManagementAddress {
continue
}
for _, sig := range txn.Signatures {
Expand Down Expand Up @@ -861,7 +861,7 @@ func validateFoundationUpdate(ms *MidState, txn types.V2Transaction) error {
return nil
}
for _, in := range txn.SiacoinInputs {
if in.Parent.SiacoinOutput.Address == ms.base.FoundationPrimaryAddress {
if in.Parent.SiacoinOutput.Address == ms.base.FoundationManagementAddress {
return nil
}
}
Expand Down
Loading