Skip to content

Commit

Permalink
Merge pull request #237 from SiaFoundation/waive-subsidy
Browse files Browse the repository at this point in the history
Allow Foundation subsidy to be waived
  • Loading branch information
n8maninger authored Dec 2, 2024
2 parents 0f56b68 + 5cc8a0e commit b5ca54b
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 46 deletions.
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

0 comments on commit b5ca54b

Please sign in to comment.