diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 29ee9a4c6..6239ad89f 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -93,6 +93,9 @@ type Blockchain struct { // Number of headers stored in the chain file. storedHeaderCount uint32 + generationAmount []int + decrementInterval int + // All operations on headerList must be called from an // headersOp to be routine safe. headerList *HeaderHashList @@ -154,6 +157,9 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L memPool: mempool.NewMemPool(cfg.MemPoolSize), keyCache: make(map[util.Uint160]map[string]*keys.PublicKey), log: log, + + generationAmount: genAmount, + decrementInterval: decrementInterval, } if err := bc.init(); err != nil { @@ -1033,6 +1039,55 @@ func (bc *Blockchain) GetConfig() config.ProtocolConfiguration { return bc.config } +// CalculateClaimable calculates the amount of GAS which can be claimed for a transaction with value. +// First return value is GAS generated between startHeight and endHeight. +// Second return value is GAS returned from accumulated SystemFees between startHeight and endHeight. +func (bc *Blockchain) CalculateClaimable(value util.Fixed8, startHeight, endHeight uint32) (util.Fixed8, util.Fixed8, error) { + var amount util.Fixed8 + di := uint32(bc.decrementInterval) + + ustart := startHeight / di + if genSize := uint32(len(bc.generationAmount)); ustart < genSize { + uend := endHeight / di + iend := endHeight % di + if uend >= genSize { + uend = genSize - 1 + iend = di + } else if iend == 0 { + uend-- + iend = di + } + + istart := startHeight % di + for ustart < uend { + amount += util.Fixed8(di-istart) * util.Fixed8(bc.generationAmount[ustart]) + ustart++ + istart = 0 + } + + amount += util.Fixed8(iend-istart) * util.Fixed8(bc.generationAmount[ustart]) + } + + var sysFeeTotal util.Fixed8 + if startHeight == 0 { + startHeight++ + } + for i := startHeight; i < endHeight; i++ { + h := bc.GetHeaderHash(int(i)) + b, err := bc.GetBlock(h) + if err != nil { + return 0, 0, err + } + for _, tx := range b.Transactions { + sysFeeTotal += bc.SystemFee(tx) + } + } + + sysFeeTotal /= 100000000 + ratio := value / 100000000 + return amount * ratio, sysFeeTotal * ratio, nil +} + // References maps transaction's inputs into a slice of InOuts, effectively // joining each Input with the corresponding Output. // @TODO: unfortunately we couldn't attach this method to the Transaction struct in the diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 523f3b448..0f1f38888 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -175,6 +175,46 @@ func TestGetTransaction(t *testing.T) { } } +func TestGetClaimable(t *testing.T) { + bc := newTestChain(t) + + _, _, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 2) + require.Error(t, err) + + bc.generationAmount = []int{4, 3, 2, 1} + bc.decrementInterval = 2 + _, err = bc.genBlocks(10) + require.NoError(t, err) + + t.Run("first generation period", func(t *testing.T) { + amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 2) + require.NoError(t, err) + require.EqualValues(t, 8, amount) + require.EqualValues(t, 0, sysfee) + }) + + t.Run("a number of full periods", func(t *testing.T) { + amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 0, 6) + require.NoError(t, err) + require.EqualValues(t, 4+4+3+3+2+2, amount) + require.EqualValues(t, 0, sysfee) + }) + + t.Run("start from the 2-nd block", func(t *testing.T) { + amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 1, 7) + require.NoError(t, err) + require.EqualValues(t, 4+3+3+2+2+1, amount) + require.EqualValues(t, 0, sysfee) + }) + + t.Run("end height after generation has ended", func(t *testing.T) { + amount, sysfee, err := bc.CalculateClaimable(util.Fixed8FromInt64(1), 1, 10) + require.NoError(t, err) + require.EqualValues(t, 4+3+3+2+2+1+1, amount) + require.EqualValues(t, 0, sysfee) + }) +} + func TestClose(t *testing.T) { defer func() { r := recover() diff --git a/pkg/core/blockchainer.go b/pkg/core/blockchainer.go index bbe3b0c6b..d5368dd28 100644 --- a/pkg/core/blockchainer.go +++ b/pkg/core/blockchainer.go @@ -20,6 +20,7 @@ type Blockchainer interface { AddHeaders(...*block.Header) error AddBlock(*block.Block) error BlockHeight() uint32 + CalculateClaimable(value util.Fixed8, startHeight, endHeight uint32) (util.Fixed8, util.Fixed8, error) Close() HeaderHeight() uint32 GetBlock(hash util.Uint256) (*block.Block, error) diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 1f72bee0d..3133a9f5c 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -32,6 +32,9 @@ func (chain testChain) ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithF func (chain testChain) GetConfig() config.ProtocolConfiguration { panic("TODO") } +func (chain testChain) CalculateClaimable(util.Fixed8, uint32, uint32) (util.Fixed8, util.Fixed8, error) { + panic("TODO") +} func (chain testChain) References(t *transaction.Transaction) ([]transaction.InOut, error) { panic("TODO")