From 1e026aac2a976ef55a4c67952f25bf1326e06efc Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Sun, 1 Oct 2023 18:38:00 +0200 Subject: [PATCH 1/2] Fix delegation stake change calculation --- pkg/protocol/engine/ledger/ledger/ledger.go | 32 ++++++++++++++------- pkg/tests/accounts_test.go | 5 ++-- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/pkg/protocol/engine/ledger/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger/ledger.go index f9bf12815..fc8b7d9aa 100644 --- a/pkg/protocol/engine/ledger/ledger/ledger.go +++ b/pkg/protocol/engine/ledger/ledger/ledger.go @@ -473,7 +473,7 @@ func (l *Ledger) processCreatedAndConsumedAccountOutputs(stateDiff mempool.State consumedAccounts = make(map[iotago.AccountID]*utxoledger.Output) destroyedAccounts = ds.NewSet[iotago.AccountID]() - createdAccountDelegation := make(map[iotago.ChainID]*iotago.DelegationOutput) + newAccountDelegation := make(map[iotago.ChainID]*iotago.DelegationOutput) stateDiff.CreatedStates().ForEachKey(func(outputID iotago.OutputID) bool { createdOutput, errOutput := l.Output(outputID) @@ -499,11 +499,17 @@ func (l *Ledger) processCreatedAndConsumedAccountOutputs(stateDiff mempool.State } createdAccounts[accountID] = createdOutput - case iotago.OutputDelegation: - // the delegation output was created => determine later if we need to add the stake to the validator - delegation, _ := createdOutput.Output().(*iotago.DelegationOutput) - createdAccountDelegation[delegation.DelegationID] = delegation + // The Delegation Output output was created or transitioned => determine later if we need to add the stake to the validator. + delegationOutput, _ := createdOutput.Output().(*iotago.DelegationOutput) + delegationID := delegationOutput.DelegationID + // Check if the output was newly created or if it was transitioned to delayed claiming. + // Zeroed Delegation ID => newly created. + // Non-Zero Delegation ID => delayed claiming transition. + if delegationID == iotago.EmptyDelegationID() { + delegationID = iotago.DelegationIDFromOutputID(outputID) + newAccountDelegation[delegationID] = delegationOutput + } case iotago.OutputBasic: // if a basic output is sent to an implicit account creation address, we need to create the account @@ -545,13 +551,19 @@ func (l *Ledger) processCreatedAndConsumedAccountOutputs(stateDiff mempool.State case iotago.OutputDelegation: delegationOutput, _ := spentOutput.Output().(*iotago.DelegationOutput) + delegationID := delegationOutput.DelegationID + if delegationID == iotago.EmptyDelegationID() { + delegationID = iotago.DelegationIDFromOutputID(outputID) + } // TODO: do we have a testcase that checks transitioning a delegation output twice in the same slot? - if _, createdDelegationExists := createdAccountDelegation[delegationOutput.DelegationID]; createdDelegationExists { + if _, createdDelegationExists := newAccountDelegation[delegationID]; createdDelegationExists { // the delegation output was created and destroyed in the same slot => do not track the delegation as newly created - delete(createdAccountDelegation, delegationOutput.DelegationID) - } else { - // the delegation output was destroyed => subtract the stake from the validator account + delete(newAccountDelegation, delegationID) + } else if delegationOutput.DelegationID.Empty() { + // The Delegation Output was destroyed or transitioned to delayed claiming => subtract the stake from the validator account. + // We check for a non-zero Delegation ID so we don't remove the stake twice when the output was destroyed + // in delayed claiming, since we already subtract it when the output is transitioned. accountDiff := getAccountDiff(accountDiffs, delegationOutput.ValidatorAddress.AccountID()) accountDiff.DelegationStakeChange -= int64(delegationOutput.DelegatedAmount) } @@ -567,7 +579,7 @@ func (l *Ledger) processCreatedAndConsumedAccountOutputs(stateDiff mempool.State return true }) - for _, delegationOutput := range createdAccountDelegation { + for _, delegationOutput := range newAccountDelegation { // the delegation output was newly created and not transitioned/destroyed => add the stake to the validator account accountDiff := getAccountDiff(accountDiffs, delegationOutput.ValidatorAddress.AccountID()) accountDiff.DelegationStakeChange += int64(delegationOutput.DelegatedAmount) diff --git a/pkg/tests/accounts_test.go b/pkg/tests/accounts_test.go index fca864f9a..23585ccec 100644 --- a/pkg/tests/accounts_test.go +++ b/pkg/tests/accounts_test.go @@ -270,6 +270,7 @@ func Test_TransitionAccount(t *testing.T) { latestParent = ts.CommitUntilSlot(block4Slot, activeNodes, block4) + // Transitioning to delayed claiming effectively removes the delegation, so we expect a negative delegation stake change. ts.AssertAccountDiff(newAccountOutput.AccountID, block4Slot, &model.AccountDiff{ BICChange: 0, PreviousUpdatedTime: 0, @@ -280,7 +281,7 @@ func Test_TransitionAccount(t *testing.T) { ValidatorStakeChange: 0, StakeEndEpochChange: 0, FixedCostChange: 0, - DelegationStakeChange: 0, + DelegationStakeChange: -int64(delegatedAmount), }, false, ts.Nodes()...) ts.AssertAccountData(&accounts.AccountData{ @@ -291,7 +292,7 @@ func Test_TransitionAccount(t *testing.T) { BlockIssuerKeys: iotago.NewBlockIssuerKeys(newAccountBlockIssuerKey), StakeEndEpoch: 10, FixedCost: 421, - DelegationStake: iotago.BaseToken(delegatedAmount), + DelegationStake: iotago.BaseToken(0), ValidatorStake: 10000, }, ts.Nodes()...) From 0c2bef578ea57d3d8e8f39111651c4e5dab1b676 Mon Sep 17 00:00:00 2001 From: Philipp Gackstatter Date: Mon, 2 Oct 2023 13:37:04 +0200 Subject: [PATCH 2/2] Update pkg/protocol/engine/ledger/ledger/ledger.go Co-authored-by: muXxer --- pkg/protocol/engine/ledger/ledger/ledger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/protocol/engine/ledger/ledger/ledger.go b/pkg/protocol/engine/ledger/ledger/ledger.go index fc8b7d9aa..a216055f9 100644 --- a/pkg/protocol/engine/ledger/ledger/ledger.go +++ b/pkg/protocol/engine/ledger/ledger/ledger.go @@ -500,7 +500,7 @@ func (l *Ledger) processCreatedAndConsumedAccountOutputs(stateDiff mempool.State createdAccounts[accountID] = createdOutput case iotago.OutputDelegation: - // The Delegation Output output was created or transitioned => determine later if we need to add the stake to the validator. + // The DelegationOutput was created or transitioned => determine later if we need to add the stake to the validator. delegationOutput, _ := createdOutput.Output().(*iotago.DelegationOutput) delegationID := delegationOutput.DelegationID // Check if the output was newly created or if it was transitioned to delayed claiming.