mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-06 22:26:47 +00:00
Use separate depth than finality depth for merge set calculations after HF (#2013)
* Use separate than finality depth for merge set calculations after HF * Add comments and edit error messages * Fix TestValidateTransactionInContextAndPopulateFee * Don't disconnect from node if isViolatingBoundedMergeDepth * Use new merge root for virtual pick parents; apply HF1 daa score split for validation only * Use `blue work` heuristic to skip irrelevant relay blocks * Minor * Make sure virtual's merge depth root is a real block * For ghostdag data we always use the non-trusted data * Fix TestBoundedMergeDepth and in IBD use VirtualMergeDepthRoot instead of MergeDepthRoot * Update HF1DAAScore * Make sure merge root and finality are called + avoid calculating virtual root twice * Update block version to 1 after HF * Update to v0.12.0 Co-authored-by: msutton <mikisiton2@gmail.com>
This commit is contained in:
parent
ada559f007
commit
2b395e34b1
@ -7,6 +7,7 @@ import (
|
|||||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||||
"github.com/kaspanet/kaspad/domain"
|
"github.com/kaspanet/kaspad/domain"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
@ -139,6 +140,26 @@ func (flow *handleRelayInvsFlow) start() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test bounded merge depth to avoid requesting irrelevant data which cannot be merged under virtual
|
||||||
|
virtualMergeDepthRoot, err := flow.Domain().Consensus().VirtualMergeDepthRoot()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !virtualMergeDepthRoot.Equal(model.VirtualGenesisBlockHash) {
|
||||||
|
mergeDepthRootHeader, err := flow.Domain().Consensus().GetBlockHeader(virtualMergeDepthRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Since `BlueWork` respects topology, this condition means that the relay
|
||||||
|
// block is not in the future of virtual's merge depth root, and thus cannot be merged unless
|
||||||
|
// other valid blocks Kosherize it, in which case it will be obtained once the merger is relayed
|
||||||
|
if block.Header.BlueWork().Cmp(mergeDepthRootHeader.BlueWork()) <= 0 {
|
||||||
|
log.Debugf("Block %s has lower blue work than virtual's merge root %s (%d <= %d), hence we are skipping it",
|
||||||
|
inv.Hash, virtualMergeDepthRoot, block.Header.BlueWork(), mergeDepthRootHeader.BlueWork())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Debugf("Processing block %s", inv.Hash)
|
log.Debugf("Processing block %s", inv.Hash)
|
||||||
oldVirtualInfo, err := flow.Domain().Consensus().GetVirtualInfo()
|
oldVirtualInfo, err := flow.Domain().Consensus().GetVirtualInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,6 +2,10 @@ package libkaspawallet_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -310,3 +314,239 @@ func TestP2PK(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaxSompi(t *testing.T) {
|
||||||
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
|
params := &consensusConfig.Params
|
||||||
|
cfg := *consensusConfig
|
||||||
|
cfg.BlockCoinbaseMaturity = 0
|
||||||
|
cfg.PreDeflationaryPhaseBaseSubsidy = 20e6 * constants.SompiPerKaspa
|
||||||
|
cfg.HF1DAAScore = cfg.GenesisBlock.Header.DAAScore() + 10
|
||||||
|
tc, teardown, err := consensus.NewFactory().NewTestConsensus(&cfg, "TestMaxSompi")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error setting up tc: %+v", err)
|
||||||
|
}
|
||||||
|
defer teardown(false)
|
||||||
|
|
||||||
|
const numKeys = 1
|
||||||
|
mnemonics := make([]string, numKeys)
|
||||||
|
publicKeys := make([]string, numKeys)
|
||||||
|
for i := 0; i < numKeys; i++ {
|
||||||
|
var err error
|
||||||
|
mnemonics[i], err = libkaspawallet.CreateMnemonic()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateMnemonic: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
publicKeys[i], err = libkaspawallet.MasterPublicKeyFromMnemonic(&cfg.Params, mnemonics[i], false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const minimumSignatures = 1
|
||||||
|
path := "m/1/2/3"
|
||||||
|
address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, path, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Address: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptPublicKey, err := txscript.PayToAddrScript(address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("PayToAddrScript: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
coinbaseData := &externalapi.DomainCoinbaseData{
|
||||||
|
ScriptPublicKey: scriptPublicKey,
|
||||||
|
ExtraData: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{cfg.GenesisHash}, coinbaseData, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock2Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock1Hash}, coinbaseData, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock3Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock2Hash}, coinbaseData, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock4Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock3Hash}, coinbaseData, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock2, err := tc.GetBlock(fundingBlock2Hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock3, err := tc.GetBlock(fundingBlock3Hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fundingBlock4, err := tc.GetBlock(fundingBlock4Hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlock4Hash}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block1, err := tc.GetBlock(block1Hash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txOut1 := fundingBlock2.Transactions[0].Outputs[0]
|
||||||
|
txOut2 := fundingBlock3.Transactions[0].Outputs[0]
|
||||||
|
txOut3 := fundingBlock4.Transactions[0].Outputs[0]
|
||||||
|
txOut4 := block1.Transactions[0].Outputs[0]
|
||||||
|
selectedUTXOsForTxWithLargeInputAmount := []*libkaspawallet.UTXO{
|
||||||
|
{
|
||||||
|
Outpoint: &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensushashing.TransactionID(fundingBlock2.Transactions[0]),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
UTXOEntry: utxo.NewUTXOEntry(txOut1.Value, txOut1.ScriptPublicKey, true, 0),
|
||||||
|
DerivationPath: path,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Outpoint: &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensushashing.TransactionID(fundingBlock3.Transactions[0]),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
UTXOEntry: utxo.NewUTXOEntry(txOut2.Value, txOut2.ScriptPublicKey, true, 0),
|
||||||
|
DerivationPath: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unsignedTxWithLargeInputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
||||||
|
[]*libkaspawallet.Payment{{
|
||||||
|
Address: address,
|
||||||
|
Amount: 10,
|
||||||
|
}}, selectedUTXOsForTxWithLargeInputAmount)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateUnsignedTransactions: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTxWithLargeInputAmount, err := libkaspawallet.Sign(params, mnemonics, unsignedTxWithLargeInputAmount, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txWithLargeInputAmount, err := libkaspawallet.ExtractTransaction(signedTxWithLargeInputAmount, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExtractTransaction: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, virtualChangeSet, err := tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{txWithLargeInputAmount})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addedUTXO1 := &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensushashing.TransactionID(txWithLargeInputAmount),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
if virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO1) {
|
||||||
|
t.Fatalf("Transaction was accepted in the DAG")
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedUTXOsForTxWithLargeInputAndOutputAmount := []*libkaspawallet.UTXO{
|
||||||
|
{
|
||||||
|
Outpoint: &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensushashing.TransactionID(fundingBlock4.Transactions[0]),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
UTXOEntry: utxo.NewUTXOEntry(txOut3.Value, txOut3.ScriptPublicKey, true, 0),
|
||||||
|
DerivationPath: path,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Outpoint: &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensushashing.TransactionID(block1.Transactions[0]),
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
UTXOEntry: utxo.NewUTXOEntry(txOut4.Value, txOut4.ScriptPublicKey, true, 0),
|
||||||
|
DerivationPath: path,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
unsignedTxWithLargeInputAndOutputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
||||||
|
[]*libkaspawallet.Payment{{
|
||||||
|
Address: address,
|
||||||
|
Amount: 22e6 * constants.SompiPerKaspa,
|
||||||
|
}}, selectedUTXOsForTxWithLargeInputAndOutputAmount)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateUnsignedTransactions: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTxWithLargeInputAndOutputAmount, err := libkaspawallet.Sign(params, mnemonics, unsignedTxWithLargeInputAndOutputAmount, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txWithLargeInputAndOutputAmount, err := libkaspawallet.ExtractTransaction(signedTxWithLargeInputAndOutputAmount, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExtractTransaction: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = tc.AddBlock([]*externalapi.DomainHash{block1Hash}, nil, []*externalapi.DomainTransaction{txWithLargeInputAndOutputAmount})
|
||||||
|
if !errors.Is(err, ruleerrors.ErrBadTxOutValue) {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tip := block1Hash
|
||||||
|
for {
|
||||||
|
tip, _, err = tc.AddBlock([]*externalapi.DomainHash{tip}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedTip, err := tc.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetVirtualDAAScore: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
daaScore, err := tc.DAABlocksStore().DAAScore(tc.DatabaseContext(), model.NewStagingArea(), selectedTip)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("DAAScore: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if daaScore >= cfg.HF1DAAScore {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tip, virtualChangeSet, err = tc.AddBlock([]*externalapi.DomainHash{tip}, nil, []*externalapi.DomainTransaction{txWithLargeInputAndOutputAmount})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addedUTXO2 := &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: *consensushashing.TransactionID(txWithLargeInputAndOutputAmount),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO2) {
|
||||||
|
t.Fatalf("txWithLargeInputAndOutputAmount weren't accepted in the DAG")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, virtualChangeSet, err = tc.AddBlock([]*externalapi.DomainHash{tip}, nil, []*externalapi.DomainTransaction{txWithLargeInputAmount})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO1) {
|
||||||
|
t.Fatalf("txWithLargeInputAmount wasn't accepted in the DAG")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -182,7 +182,12 @@ func (s *consensus) ValidateTransactionAndPopulateWithConsensusData(transaction
|
|||||||
|
|
||||||
stagingArea := model.NewStagingArea()
|
stagingArea := model.NewStagingArea()
|
||||||
|
|
||||||
err := s.transactionValidator.ValidateTransactionInIsolation(transaction)
|
daaScore, err := s.daaBlocksStore.DAAScore(s.databaseContext, stagingArea, model.VirtualBlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.transactionValidator.ValidateTransactionInIsolation(transaction, daaScore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -881,3 +886,11 @@ func (s *consensus) IsChainBlock(blockHash *externalapi.DomainHash) (bool, error
|
|||||||
|
|
||||||
return s.dagTopologyManagers[0].IsInSelectedParentChainOf(stagingArea, blockHash, virtualGHOSTDAGData.SelectedParent())
|
return s.dagTopologyManagers[0].IsInSelectedParentChainOf(stagingArea, blockHash, virtualGHOSTDAGData.SelectedParent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *consensus) VirtualMergeDepthRoot() (*externalapi.DomainHash, error) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
stagingArea := model.NewStagingArea()
|
||||||
|
return s.mergeDepthManager.VirtualMergeDepthRoot(stagingArea)
|
||||||
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package mergedepthrootstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mergeDepthRootStagingShard struct {
|
||||||
|
store *mergeDepthRootStore
|
||||||
|
toAdd map[externalapi.DomainHash]*externalapi.DomainHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrs *mergeDepthRootStore) stagingShard(stagingArea *model.StagingArea) *mergeDepthRootStagingShard {
|
||||||
|
return stagingArea.GetOrCreateShard(mdrs.shardID, func() model.StagingShard {
|
||||||
|
return &mergeDepthRootStagingShard{
|
||||||
|
store: mdrs,
|
||||||
|
toAdd: make(map[externalapi.DomainHash]*externalapi.DomainHash),
|
||||||
|
}
|
||||||
|
}).(*mergeDepthRootStagingShard)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrss *mergeDepthRootStagingShard) Commit(dbTx model.DBTransaction) error {
|
||||||
|
for hash, mergeDepthRoot := range mdrss.toAdd {
|
||||||
|
err := dbTx.Put(mdrss.store.hashAsKey(&hash), mergeDepthRoot.ByteSlice())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mdrss.store.cache.Add(&hash, mergeDepthRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrss *mergeDepthRootStagingShard) isStaged() bool {
|
||||||
|
return len(mdrss.toAdd) == 0
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package mergedepthrootstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||||
|
"github.com/kaspanet/kaspad/util/staging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bucketName = []byte("merge-depth-roots")
|
||||||
|
|
||||||
|
type mergeDepthRootStore struct {
|
||||||
|
shardID model.StagingShardID
|
||||||
|
cache *lrucache.LRUCache
|
||||||
|
bucket model.DBBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// New instantiates a new MergeDepthRootStore
|
||||||
|
func New(prefixBucket model.DBBucket, cacheSize int, preallocate bool) model.MergeDepthRootStore {
|
||||||
|
return &mergeDepthRootStore{
|
||||||
|
shardID: staging.GenerateShardingID(),
|
||||||
|
cache: lrucache.New(cacheSize, preallocate),
|
||||||
|
bucket: prefixBucket.Bucket(bucketName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrs *mergeDepthRootStore) StageMergeDepthRoot(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, root *externalapi.DomainHash) {
|
||||||
|
stagingShard := mdrs.stagingShard(stagingArea)
|
||||||
|
|
||||||
|
stagingShard.toAdd[*blockHash] = root
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrs *mergeDepthRootStore) MergeDepthRoot(dbContext model.DBReader, stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
|
||||||
|
stagingShard := mdrs.stagingShard(stagingArea)
|
||||||
|
|
||||||
|
if root, ok := stagingShard.toAdd[*blockHash]; ok {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if root, ok := mdrs.cache.Get(blockHash); ok {
|
||||||
|
return root.(*externalapi.DomainHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rootBytes, err := dbContext.Get(mdrs.hashAsKey(blockHash))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
root, err := externalapi.NewDomainHashFromByteSlice(rootBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mdrs.cache.Add(blockHash, root)
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrs *mergeDepthRootStore) IsStaged(stagingArea *model.StagingArea) bool {
|
||||||
|
return mdrs.stagingShard(stagingArea).isStaged()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdrs *mergeDepthRootStore) hashAsKey(hash *externalapi.DomainHash) model.DBKey {
|
||||||
|
return mdrs.bucket.Key(hash.ByteSlice())
|
||||||
|
}
|
@ -3,6 +3,7 @@ package consensus
|
|||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockwindowheapslicestore"
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockwindowheapslicestore"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/datastructures/daawindowstore"
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/daawindowstore"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/mergedepthrootstore"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/blockparentbuilder"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/blockparentbuilder"
|
||||||
parentssanager "github.com/kaspanet/kaspad/domain/consensus/processes/parentsmanager"
|
parentssanager "github.com/kaspanet/kaspad/domain/consensus/processes/parentsmanager"
|
||||||
@ -127,6 +128,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
pruningWindowSizePlusFinalityDepthForCache := int(config.PruningDepth() + config.FinalityDepth())
|
pruningWindowSizePlusFinalityDepthForCache := int(config.PruningDepth() + config.FinalityDepth())
|
||||||
|
|
||||||
// Data Structures
|
// Data Structures
|
||||||
|
mergeDepthRootStore := mergedepthrootstore.New(prefixBucket, 200, preallocateCaches)
|
||||||
daaWindowStore := daawindowstore.New(prefixBucket, 10_000, preallocateCaches)
|
daaWindowStore := daawindowstore.New(prefixBucket, 10_000, preallocateCaches)
|
||||||
acceptanceDataStore := acceptancedatastore.New(prefixBucket, 200, preallocateCaches)
|
acceptanceDataStore := acceptancedatastore.New(prefixBucket, 200, preallocateCaches)
|
||||||
blockStore, err := blockstore.New(dbManager, prefixBucket, 200, preallocateCaches)
|
blockStore, err := blockstore.New(dbManager, prefixBucket, 200, preallocateCaches)
|
||||||
@ -215,7 +217,8 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
pastMedianTimeManager,
|
pastMedianTimeManager,
|
||||||
ghostdagDataStore,
|
ghostdagDataStore,
|
||||||
daaBlocksStore,
|
daaBlocksStore,
|
||||||
txMassCalculator)
|
txMassCalculator,
|
||||||
|
config.HF1DAAScore)
|
||||||
difficultyManager := f.difficultyConstructor(
|
difficultyManager := f.difficultyConstructor(
|
||||||
dbManager,
|
dbManager,
|
||||||
ghostdagManager,
|
ghostdagManager,
|
||||||
@ -261,7 +264,14 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
dagTopologyManager,
|
dagTopologyManager,
|
||||||
dagTraversalManager,
|
dagTraversalManager,
|
||||||
finalityManager,
|
finalityManager,
|
||||||
ghostdagDataStore)
|
genesisHash,
|
||||||
|
config.MergeDepth,
|
||||||
|
config.HF1DAAScore,
|
||||||
|
ghostdagDataStore,
|
||||||
|
mergeDepthRootStore,
|
||||||
|
daaBlocksStore,
|
||||||
|
pruningStore,
|
||||||
|
finalityStore)
|
||||||
consensusStateManager, err := consensusstatemanager.New(
|
consensusStateManager, err := consensusstatemanager.New(
|
||||||
dbManager,
|
dbManager,
|
||||||
config.MaxBlockParents,
|
config.MaxBlockParents,
|
||||||
@ -334,7 +344,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
config.MaxBlockParents,
|
config.MaxBlockParents,
|
||||||
config.TimestampDeviationTolerance,
|
config.TimestampDeviationTolerance,
|
||||||
config.TargetTimePerBlock,
|
config.TargetTimePerBlock,
|
||||||
config.IgnoreHeaderMass,
|
config.HF1DAAScore,
|
||||||
config.MaxBlockLevel,
|
config.MaxBlockLevel,
|
||||||
|
|
||||||
dbManager,
|
dbManager,
|
||||||
@ -383,6 +393,7 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
blockBuilder := blockbuilder.New(
|
blockBuilder := blockbuilder.New(
|
||||||
dbManager,
|
dbManager,
|
||||||
genesisHash,
|
genesisHash,
|
||||||
|
config.HF1DAAScore,
|
||||||
|
|
||||||
difficultyManager,
|
difficultyManager,
|
||||||
pastMedianTimeManager,
|
pastMedianTimeManager,
|
||||||
|
@ -3,8 +3,6 @@ package consensus_test
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus"
|
"github.com/kaspanet/kaspad/domain/consensus"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||||
@ -12,6 +10,9 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFinality(t *testing.T) {
|
func TestFinality(t *testing.T) {
|
||||||
@ -179,15 +180,21 @@ func TestFinality(t *testing.T) {
|
|||||||
|
|
||||||
func TestBoundedMergeDepth(t *testing.T) {
|
func TestBoundedMergeDepth(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
|
rd := rand.New(rand.NewSource(0))
|
||||||
// Set finalityInterval to 50 blocks, so that test runs quickly
|
// Set finalityInterval to 50 blocks, so that test runs quickly
|
||||||
consensusConfig.K = 5
|
consensusConfig.K = 5
|
||||||
consensusConfig.FinalityDuration = 7 * consensusConfig.TargetTimePerBlock
|
consensusConfig.MergeDepth = 7
|
||||||
finalityInterval := int(consensusConfig.FinalityDepth())
|
consensusConfig.FinalityDuration = 20 * consensusConfig.TargetTimePerBlock
|
||||||
|
consensusConfig.HF1DAAScore = consensusConfig.GenesisBlock.Header.DAAScore() + 200
|
||||||
|
|
||||||
if int(consensusConfig.K) >= finalityInterval {
|
if uint64(consensusConfig.K) >= consensusConfig.FinalityDepth() {
|
||||||
t.Fatal("K must be smaller than finality duration for this test to run")
|
t.Fatal("K must be smaller than finality duration for this test to run")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if uint64(consensusConfig.K) >= consensusConfig.MergeDepth {
|
||||||
|
t.Fatal("K must be smaller than merge depth for this test to run")
|
||||||
|
}
|
||||||
|
|
||||||
checkViolatingMergeDepth := func(consensus testapi.TestConsensus, parents []*externalapi.DomainHash) (*externalapi.DomainBlock, bool) {
|
checkViolatingMergeDepth := func(consensus testapi.TestConsensus, parents []*externalapi.DomainHash) (*externalapi.DomainBlock, bool) {
|
||||||
block, _, err := consensus.BuildBlockWithParents(parents, nil, nil)
|
block, _, err := consensus.BuildBlockWithParents(parents, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -217,7 +224,7 @@ func TestBoundedMergeDepth(t *testing.T) {
|
|||||||
buildAndInsertBlock := func(consensus testapi.TestConsensus, parentHashes []*externalapi.DomainHash) *externalapi.DomainBlock {
|
buildAndInsertBlock := func(consensus testapi.TestConsensus, parentHashes []*externalapi.DomainHash) *externalapi.DomainBlock {
|
||||||
block, _, err := consensus.BuildBlockWithParents(parentHashes, nil, nil)
|
block, _, err := consensus.BuildBlockWithParents(parentHashes, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Failed building block: %v", err)
|
t.Fatalf("TestBoundedMergeDepth: Failed building block: %+v", err)
|
||||||
}
|
}
|
||||||
_, err = consensus.ValidateAndInsertBlock(block, true)
|
_, err = consensus.ValidateAndInsertBlock(block, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -236,194 +243,296 @@ func TestBoundedMergeDepth(t *testing.T) {
|
|||||||
return blockInfo.BlockStatus
|
return blockInfo.BlockStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
factory := consensus.NewFactory()
|
syncConsensuses := func(tcSyncer, tcSyncee testapi.TestConsensus) {
|
||||||
consensusBuild, teardownFunc1, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestBuild")
|
syncerVirtualSelectedParent, err := tcSyncer.GetVirtualSelectedParent()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
t.Fatalf("GetVirtualSelectedParent: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
missingHeaderHashes, _, err := tcSyncer.GetHashesBetween(consensusConfig.GenesisHash, syncerVirtualSelectedParent, math.MaxUint64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetHashesBetween: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, blocksHash := range missingHeaderHashes {
|
||||||
|
blockInfo, err := tcSyncee.GetBlockInfo(blocksHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlockInfo: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockInfo.Exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := tcSyncer.GetBlock(blocksHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlockHeader: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tcSyncee.ValidateAndInsertBlock(block, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ValidateAndInsertBlock %d: %+v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synceeVirtualSelectedParent, err := tcSyncee.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Tips: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !syncerVirtualSelectedParent.Equal(synceeVirtualSelectedParent) {
|
||||||
|
t.Fatalf("Syncee's selected tip is %s while syncer's is %s", synceeVirtualSelectedParent, syncerVirtualSelectedParent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory := consensus.NewFactory()
|
||||||
consensusReal, teardownFunc2, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestReal")
|
consensusReal, teardownFunc2, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestReal")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
||||||
}
|
}
|
||||||
defer teardownFunc2(false)
|
defer teardownFunc2(false)
|
||||||
|
|
||||||
// Create a block on top on genesis
|
test := func(depth uint64, root *externalapi.DomainHash, checkVirtual, isRealDepth bool) {
|
||||||
block1 := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{consensusConfig.GenesisHash})
|
consensusBuild, teardownFunc1, err := factory.NewTestConsensus(consensusConfig, "TestBoundedMergeTestBuild")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Error setting up consensus: %+v", err)
|
||||||
|
}
|
||||||
|
defer teardownFunc1(false)
|
||||||
|
consensusBuild.BlockBuilder().SetNonceCounter(rd.Uint64())
|
||||||
|
|
||||||
// Create a chain
|
syncConsensuses(consensusReal, consensusBuild)
|
||||||
selectedChain := make([]*externalapi.DomainBlock, 0, finalityInterval+1)
|
// Create a block on top on genesis
|
||||||
parent := consensushashing.BlockHash(block1)
|
block1 := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{root})
|
||||||
// Make sure this is always bigger than `blocksChain2` so it will stay the selected chain
|
|
||||||
for i := 0; i < finalityInterval+2; i++ {
|
|
||||||
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
|
||||||
selectedChain = append(selectedChain, block)
|
|
||||||
parent = consensushashing.BlockHash(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create another chain
|
// Create a chain
|
||||||
blocksChain2 := make([]*externalapi.DomainBlock, 0, finalityInterval+1)
|
selectedChain := make([]*externalapi.DomainBlock, 0, depth+1)
|
||||||
parent = consensushashing.BlockHash(block1)
|
parent := consensushashing.BlockHash(block1)
|
||||||
for i := 0; i < finalityInterval+1; i++ {
|
// Make sure this is always bigger than `blocksChain2` so it will stay the selected chain
|
||||||
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
for i := uint64(0); i < depth+2; i++ {
|
||||||
blocksChain2 = append(blocksChain2, block)
|
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
||||||
parent = consensushashing.BlockHash(block)
|
selectedChain = append(selectedChain, block)
|
||||||
}
|
parent = consensushashing.BlockHash(block)
|
||||||
|
}
|
||||||
|
|
||||||
// Teardown and assign nil to make sure we use the right DAG from here on.
|
// Create another chain
|
||||||
teardownFunc1(false)
|
blocksChain2 := make([]*externalapi.DomainBlock, 0, depth+1)
|
||||||
consensusBuild = nil
|
parent = consensushashing.BlockHash(block1)
|
||||||
|
for i := uint64(0); i < depth+1; i++ {
|
||||||
|
block := buildAndInsertBlock(consensusBuild, []*externalapi.DomainHash{parent})
|
||||||
|
blocksChain2 = append(blocksChain2, block)
|
||||||
|
parent = consensushashing.BlockHash(block)
|
||||||
|
}
|
||||||
|
|
||||||
// Now test against the real DAG
|
// Now test against the real DAG
|
||||||
// submit block1
|
// submit block1
|
||||||
processBlock(consensusReal, block1, "block1")
|
processBlock(consensusReal, block1, "block1")
|
||||||
|
|
||||||
// submit chain1
|
// submit chain1
|
||||||
for i, block := range selectedChain {
|
for i, block := range selectedChain {
|
||||||
processBlock(consensusReal, block, fmt.Sprintf("selectedChain block No %d", i))
|
processBlock(consensusReal, block, fmt.Sprintf("selectedChain block No %d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
// submit chain2
|
// submit chain2
|
||||||
for i, block := range blocksChain2 {
|
for i, block := range blocksChain2 {
|
||||||
processBlock(consensusReal, block, fmt.Sprintf("blocksChain2 block No %d", i))
|
processBlock(consensusReal, block, fmt.Sprintf("blocksChain2 block No %d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
// submit a block pointing at tip(chain1) and on first block in chain2 directly
|
// submit a block pointing at tip(chain1) and on first block in chain2 directly
|
||||||
mergeDepthViolatingBlockBottom, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[0]), consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
mergeDepthViolatingBlockBottom, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[0]), consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
||||||
if !isViolatingMergeDepth {
|
if isViolatingMergeDepth != isRealDepth {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected mergeDepthViolatingBlockBottom to violate merge depth")
|
t.Fatalf("TestBoundedMergeDepth: Expects isViolatingMergeDepth to be %t", isRealDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// submit a block pointing at tip(chain1) and tip(chain2) should also obviously violate merge depth (this points at first block in chain2 indirectly)
|
// submit a block pointing at tip(chain1) and tip(chain2) should also obviously violate merge depth (this points at first block in chain2 indirectly)
|
||||||
mergeDepthViolatingTop, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[len(blocksChain2)-1]), consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
mergeDepthViolatingTop, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[len(blocksChain2)-1]), consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
||||||
if !isViolatingMergeDepth {
|
if isViolatingMergeDepth != isRealDepth {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected mergeDepthViolatingTop to violate merge depth")
|
t.Fatalf("TestBoundedMergeDepth: Expects isViolatingMergeDepth to be %t", isRealDepth)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the location of the parents in the slices need to be both `-X` so the `selectedChain` one will have higher blueScore (it's a chain longer by 1)
|
// the location of the parents in the slices need to be both `-X` so the `selectedChain` one will have higher blueScore (it's a chain longer by 1)
|
||||||
kosherizingBlock, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[len(blocksChain2)-3]), consensushashing.BlockHash(selectedChain[len(selectedChain)-3])})
|
kosherizingBlock, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(blocksChain2[len(blocksChain2)-3]), consensushashing.BlockHash(selectedChain[len(selectedChain)-3])})
|
||||||
kosherizingBlockHash := consensushashing.BlockHash(kosherizingBlock)
|
kosherizingBlockHash := consensushashing.BlockHash(kosherizingBlock)
|
||||||
if isViolatingMergeDepth {
|
if isViolatingMergeDepth {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected blueKosherizingBlock to not violate merge depth")
|
t.Fatalf("TestBoundedMergeDepth: Expected blueKosherizingBlock to not violate merge depth")
|
||||||
}
|
}
|
||||||
|
|
||||||
stagingArea := model.NewStagingArea()
|
if checkVirtual {
|
||||||
virtualGhotDagData, err := consensusReal.GHOSTDAGDataStore().Get(consensusReal.DatabaseContext(),
|
stagingArea := model.NewStagingArea()
|
||||||
stagingArea, model.VirtualBlockHash, false)
|
virtualGhotDagData, err := consensusReal.GHOSTDAGDataStore().Get(consensusReal.DatabaseContext(),
|
||||||
if err != nil {
|
stagingArea, model.VirtualBlockHash, false)
|
||||||
t.Fatalf("TestBoundedMergeDepth: Failed getting the ghostdag data of the virtual: %v", err)
|
if err != nil {
|
||||||
}
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the ghostdag data of the virtual: %v", err)
|
||||||
// Make sure it's actually blue
|
}
|
||||||
found := false
|
// Make sure it's actually blue
|
||||||
for _, blue := range virtualGhotDagData.MergeSetBlues() {
|
found := false
|
||||||
if blue.Equal(kosherizingBlockHash) {
|
for _, blue := range virtualGhotDagData.MergeSetBlues() {
|
||||||
found = true
|
if blue.Equal(kosherizingBlockHash) {
|
||||||
break
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected kosherizingBlock to be blue by the virtual")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pointAtBlueKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
||||||
|
if isViolatingMergeDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected selectedTip to not violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkVirtual {
|
||||||
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !virtualSelectedParent.Equal(consensushashing.BlockHash(pointAtBlueKosherizing)) {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensushashing.BlockHash(pointAtBlueKosherizing), virtualSelectedParent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now let's make the kosherizing block red and try to merge again
|
||||||
|
tip := consensushashing.BlockHash(selectedChain[len(selectedChain)-1])
|
||||||
|
// we use k-1 because `kosherizingBlock` points at tip-2, so 2+k-1 = k+1 anticone.
|
||||||
|
for i := 0; i < int(consensusConfig.K)-1; i++ {
|
||||||
|
block := buildAndInsertBlock(consensusReal, []*externalapi.DomainHash{tip})
|
||||||
|
tip = consensushashing.BlockHash(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkVirtual {
|
||||||
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !virtualSelectedParent.Equal(tip) {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", tip, virtualSelectedParent)
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualGhotDagData, err := consensusReal.GHOSTDAGDataStore().Get(
|
||||||
|
consensusReal.DatabaseContext(), model.NewStagingArea(), model.VirtualBlockHash, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the ghostdag data of the virtual: %v", err)
|
||||||
|
}
|
||||||
|
// Make sure it's actually blue
|
||||||
|
found := false
|
||||||
|
for _, blue := range virtualGhotDagData.MergeSetBlues() {
|
||||||
|
if blue.Equal(kosherizingBlockHash) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
t.Fatalf("expected kosherizingBlock to be red by the virtual")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pointAtRedKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, tip})
|
||||||
|
if isViolatingMergeDepth != isRealDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expects isViolatingMergeDepth to be %t", isRealDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now `pointAtBlueKosherizing` itself is actually still blue, so we can still point at that even though we can't point at kosherizing directly anymore
|
||||||
|
transitiveBlueKosherizing, isViolatingMergeDepth :=
|
||||||
|
checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(pointAtBlueKosherizing), tip})
|
||||||
|
if isViolatingMergeDepth {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected transitiveBlueKosherizing to not violate merge depth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkVirtual {
|
||||||
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !virtualSelectedParent.Equal(consensushashing.BlockHash(transitiveBlueKosherizing)) {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensushashing.BlockHash(transitiveBlueKosherizing), virtualSelectedParent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lets validate the status of all the interesting blocks
|
||||||
|
if getStatus(consensusReal, pointAtBlueKosherizing) != externalapi.StatusUTXOValid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: pointAtBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, pointAtBlueKosherizing))
|
||||||
|
}
|
||||||
|
if getStatus(consensusReal, pointAtRedKosherizing) != externalapi.StatusInvalid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: pointAtRedKosherizing expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, pointAtRedKosherizing))
|
||||||
|
}
|
||||||
|
if getStatus(consensusReal, transitiveBlueKosherizing) != externalapi.StatusUTXOValid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: transitiveBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, transitiveBlueKosherizing))
|
||||||
|
}
|
||||||
|
if getStatus(consensusReal, mergeDepthViolatingBlockBottom) != externalapi.StatusInvalid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingBlockBottom expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingBlockBottom))
|
||||||
|
}
|
||||||
|
if getStatus(consensusReal, mergeDepthViolatingTop) != externalapi.StatusInvalid {
|
||||||
|
t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingTop expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingTop))
|
||||||
|
}
|
||||||
|
if getStatus(consensusReal, kosherizingBlock) != externalapi.StatusUTXOPendingVerification {
|
||||||
|
t.Fatalf("kosherizingBlock expected status '%s' but got '%s'", externalapi.StatusUTXOPendingVerification, getStatus(consensusReal, kosherizingBlock))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range blocksChain2 {
|
||||||
|
if getStatus(consensusReal, b) != externalapi.StatusUTXOPendingVerification {
|
||||||
|
t.Fatalf("blocksChain2[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOPendingVerification, getStatus(consensusReal, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, b := range selectedChain {
|
||||||
|
if getStatus(consensusReal, b) != externalapi.StatusUTXOValid {
|
||||||
|
t.Fatalf("selectedChain[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOValid, getStatus(consensusReal, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected kosherizingBlock to be blue by the virtual")
|
|
||||||
}
|
|
||||||
|
|
||||||
pointAtBlueKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, consensushashing.BlockHash(selectedChain[len(selectedChain)-1])})
|
test(consensusConfig.FinalityDepth(), consensusConfig.GenesisHash, true, true)
|
||||||
if isViolatingMergeDepth {
|
virtualDAAScore, err := consensusReal.GetVirtualDAAScore()
|
||||||
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected selectedTip to not violate merge depth")
|
|
||||||
}
|
|
||||||
|
|
||||||
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
t.Fatalf("GetVirtualDAAScore: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !virtualSelectedParent.Equal(consensushashing.BlockHash(pointAtBlueKosherizing)) {
|
if virtualDAAScore > consensusConfig.HF1DAAScore {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensushashing.BlockHash(pointAtBlueKosherizing), virtualSelectedParent)
|
t.Fatalf("Hard fork is already activated")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now let's make the kosherizing block red and try to merge again
|
tipBeforeHFActivated, err := consensusReal.GetVirtualSelectedParent()
|
||||||
tip := consensushashing.BlockHash(selectedChain[len(selectedChain)-1])
|
|
||||||
// we use k-1 because `kosherizingBlock` points at tip-2, so 2+k-1 = k+1 anticone.
|
|
||||||
for i := 0; i < int(consensusConfig.K)-1; i++ {
|
|
||||||
block := buildAndInsertBlock(consensusReal, []*externalapi.DomainHash{tip})
|
|
||||||
tip = consensushashing.BlockHash(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
virtualSelectedParent, err = consensusReal.GetVirtualSelectedParent()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
t.Fatalf("GetVirtualSelectedParent: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !virtualSelectedParent.Equal(tip) {
|
tip := tipBeforeHFActivated
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", tip, virtualSelectedParent)
|
for {
|
||||||
}
|
tip, _, err = consensusReal.AddBlock([]*externalapi.DomainHash{tip}, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed adding block: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
virtualGhotDagData, err = consensusReal.GHOSTDAGDataStore().Get(
|
daaScore, err := consensusReal.DAABlocksStore().DAAScore(consensusReal.DatabaseContext(), model.NewStagingArea(), tip)
|
||||||
consensusReal.DatabaseContext(), stagingArea, model.VirtualBlockHash, false)
|
if err != nil {
|
||||||
if err != nil {
|
t.Fatalf("Failed adding block: %+v", err)
|
||||||
t.Fatalf("TestBoundedMergeDepth: Failed getting the ghostdag data of the virtual: %v", err)
|
}
|
||||||
}
|
|
||||||
// Make sure it's actually blue
|
// We check what happens when on the transition between rules: if we merge a chain that
|
||||||
found = false
|
// started before the HF was activated, but the HF was already activated for the merging
|
||||||
for _, blue := range virtualGhotDagData.MergeSetBlues() {
|
// block, the HF rules should apply.
|
||||||
if blue.Equal(kosherizingBlockHash) {
|
// The checked block in `test` is going to have a DAA score of `tip.DAAScore + depth + 5`,
|
||||||
found = true
|
// so this is why we started to test from this depth.
|
||||||
break
|
if daaScore == consensusConfig.HF1DAAScore-consensusConfig.MergeDepth-5 {
|
||||||
|
test(consensusConfig.MergeDepth, tip, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if daaScore > consensusConfig.HF1DAAScore {
|
||||||
|
virtualSelectedParent, err := consensusReal.GetVirtualSelectedParent()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetVirtualSelectedParent: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if virtualSelectedParent.Equal(tip) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
|
||||||
t.Fatalf("expected kosherizingBlock to be red by the virtual")
|
|
||||||
}
|
|
||||||
|
|
||||||
pointAtRedKosherizing, isViolatingMergeDepth := checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{kosherizingBlockHash, tip})
|
test(consensusConfig.MergeDepth, tip, true, true)
|
||||||
if !isViolatingMergeDepth {
|
test(consensusConfig.FinalityDepth(), tipBeforeHFActivated, false, true)
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected selectedTipRedKosherize to violate merge depth")
|
test(consensusConfig.MergeDepth, tipBeforeHFActivated, false, false)
|
||||||
}
|
|
||||||
|
|
||||||
// Now `pointAtBlueKosherizing` itself is actually still blue, so we can still point at that even though we can't point at kosherizing directly anymore
|
|
||||||
transitiveBlueKosherizing, isViolatingMergeDepth :=
|
|
||||||
checkViolatingMergeDepth(consensusReal, []*externalapi.DomainHash{consensushashing.BlockHash(pointAtBlueKosherizing), tip})
|
|
||||||
if isViolatingMergeDepth {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected transitiveBlueKosherizing to not violate merge depth")
|
|
||||||
}
|
|
||||||
|
|
||||||
virtualSelectedParent, err = consensusReal.GetVirtualSelectedParent()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: Failed getting the virtual selected parent %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !virtualSelectedParent.Equal(consensushashing.BlockHash(transitiveBlueKosherizing)) {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: Expected %s to be the selectedTip but found %s instead", consensushashing.BlockHash(transitiveBlueKosherizing), virtualSelectedParent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lets validate the status of all the interesting blocks
|
|
||||||
if getStatus(consensusReal, pointAtBlueKosherizing) != externalapi.StatusUTXOValid {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: pointAtBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, pointAtBlueKosherizing))
|
|
||||||
}
|
|
||||||
if getStatus(consensusReal, pointAtRedKosherizing) != externalapi.StatusInvalid {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: pointAtRedKosherizing expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, pointAtRedKosherizing))
|
|
||||||
}
|
|
||||||
if getStatus(consensusReal, transitiveBlueKosherizing) != externalapi.StatusUTXOValid {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: transitiveBlueKosherizing expected status '%s' but got '%s'", externalapi.StatusUTXOValid, getStatus(consensusReal, transitiveBlueKosherizing))
|
|
||||||
}
|
|
||||||
if getStatus(consensusReal, mergeDepthViolatingBlockBottom) != externalapi.StatusInvalid {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingBlockBottom expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingBlockBottom))
|
|
||||||
}
|
|
||||||
if getStatus(consensusReal, mergeDepthViolatingTop) != externalapi.StatusInvalid {
|
|
||||||
t.Fatalf("TestBoundedMergeDepth: mergeDepthViolatingTop expected status '%s' but got '%s'", externalapi.StatusInvalid, getStatus(consensusReal, mergeDepthViolatingTop))
|
|
||||||
}
|
|
||||||
if getStatus(consensusReal, kosherizingBlock) != externalapi.StatusUTXOPendingVerification {
|
|
||||||
t.Fatalf("kosherizingBlock expected status '%s' but got '%s'", externalapi.StatusUTXOPendingVerification, getStatus(consensusReal, kosherizingBlock))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, b := range blocksChain2 {
|
|
||||||
if getStatus(consensusReal, b) != externalapi.StatusUTXOPendingVerification {
|
|
||||||
t.Fatalf("blocksChain2[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOPendingVerification, getStatus(consensusReal, b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, b := range selectedChain {
|
|
||||||
if getStatus(consensusReal, b) != externalapi.StatusUTXOValid {
|
|
||||||
t.Fatalf("selectedChain[%d] expected status '%s' but got '%s'", i, externalapi.StatusUTXOValid, getStatus(consensusReal, b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -53,4 +53,5 @@ type Consensus interface {
|
|||||||
TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash *DomainHash) ([]*DomainHash, error)
|
TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash *DomainHash) ([]*DomainHash, error)
|
||||||
TrustedGHOSTDAGData(blockHash *DomainHash) (*BlockGHOSTDAGData, error)
|
TrustedGHOSTDAGData(blockHash *DomainHash) (*BlockGHOSTDAGData, error)
|
||||||
IsChainBlock(blockHash *DomainHash) (bool, error)
|
IsChainBlock(blockHash *DomainHash) (bool, error)
|
||||||
|
VirtualMergeDepthRoot() (*DomainHash, error)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MergeDepthRootStore represents a store for merge depth roots
|
||||||
|
type MergeDepthRootStore interface {
|
||||||
|
Store
|
||||||
|
IsStaged(stagingArea *StagingArea) bool
|
||||||
|
StageMergeDepthRoot(stagingArea *StagingArea, blockHash *externalapi.DomainHash, root *externalapi.DomainHash)
|
||||||
|
MergeDepthRoot(dbContext DBReader, stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error)
|
||||||
|
}
|
@ -5,5 +5,7 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|||||||
// MergeDepthManager is used to validate mergeDepth for blocks
|
// MergeDepthManager is used to validate mergeDepth for blocks
|
||||||
type MergeDepthManager interface {
|
type MergeDepthManager interface {
|
||||||
CheckBoundedMergeDepth(stagingArea *StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) error
|
CheckBoundedMergeDepth(stagingArea *StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) error
|
||||||
NonBoundedMergeDepthViolatingBlues(stagingArea *StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) ([]*externalapi.DomainHash, error)
|
NonBoundedMergeDepthViolatingBlues(stagingArea *StagingArea, blockHash, mergeDepthRoot *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
|
||||||
|
VirtualMergeDepthRoot(stagingArea *StagingArea) (*externalapi.DomainHash, error)
|
||||||
|
MergeDepthRoot(stagingArea *StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) (*externalapi.DomainHash, error)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
// TransactionValidator exposes a set of validation classes, after which
|
// TransactionValidator exposes a set of validation classes, after which
|
||||||
// it's possible to determine whether a transaction is valid
|
// it's possible to determine whether a transaction is valid
|
||||||
type TransactionValidator interface {
|
type TransactionValidator interface {
|
||||||
ValidateTransactionInIsolation(transaction *externalapi.DomainTransaction) error
|
ValidateTransactionInIsolation(transaction *externalapi.DomainTransaction, povDAAScore uint64) error
|
||||||
ValidateTransactionInContextIgnoringUTXO(stagingArea *StagingArea, tx *externalapi.DomainTransaction,
|
ValidateTransactionInContextIgnoringUTXO(stagingArea *StagingArea, tx *externalapi.DomainTransaction,
|
||||||
povBlockHash *externalapi.DomainHash, povBlockPastMedianTime int64) error
|
povBlockHash *externalapi.DomainHash, povBlockPastMedianTime int64) error
|
||||||
ValidateTransactionInContextAndPopulateFee(stagingArea *StagingArea,
|
ValidateTransactionInContextAndPopulateFee(stagingArea *StagingArea,
|
||||||
|
@ -18,4 +18,6 @@ type TestBlockBuilder interface {
|
|||||||
|
|
||||||
BuildUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainBlock,
|
BuildUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainBlock,
|
||||||
error)
|
error)
|
||||||
|
|
||||||
|
SetNonceCounter(nonceCounter uint64)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
type blockBuilder struct {
|
type blockBuilder struct {
|
||||||
databaseContext model.DBManager
|
databaseContext model.DBManager
|
||||||
genesisHash *externalapi.DomainHash
|
genesisHash *externalapi.DomainHash
|
||||||
|
hf1DAAScore uint64
|
||||||
|
|
||||||
difficultyManager model.DifficultyManager
|
difficultyManager model.DifficultyManager
|
||||||
pastMedianTimeManager model.PastMedianTimeManager
|
pastMedianTimeManager model.PastMedianTimeManager
|
||||||
@ -42,6 +43,7 @@ type blockBuilder struct {
|
|||||||
func New(
|
func New(
|
||||||
databaseContext model.DBManager,
|
databaseContext model.DBManager,
|
||||||
genesisHash *externalapi.DomainHash,
|
genesisHash *externalapi.DomainHash,
|
||||||
|
hf1DAAScore uint64,
|
||||||
|
|
||||||
difficultyManager model.DifficultyManager,
|
difficultyManager model.DifficultyManager,
|
||||||
pastMedianTimeManager model.PastMedianTimeManager,
|
pastMedianTimeManager model.PastMedianTimeManager,
|
||||||
@ -63,6 +65,7 @@ func New(
|
|||||||
return &blockBuilder{
|
return &blockBuilder{
|
||||||
databaseContext: databaseContext,
|
databaseContext: databaseContext,
|
||||||
genesisHash: genesisHash,
|
genesisHash: genesisHash,
|
||||||
|
hf1DAAScore: hf1DAAScore,
|
||||||
|
|
||||||
difficultyManager: difficultyManager,
|
difficultyManager: difficultyManager,
|
||||||
pastMedianTimeManager: pastMedianTimeManager,
|
pastMedianTimeManager: pastMedianTimeManager,
|
||||||
@ -224,8 +227,13 @@ func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version := constants.BlockVersionBeforeHF1
|
||||||
|
if daaScore >= bb.hf1DAAScore {
|
||||||
|
version = constants.BlockVersionAfterHF1
|
||||||
|
}
|
||||||
|
|
||||||
return blockheader.NewImmutableBlockHeader(
|
return blockheader.NewImmutableBlockHeader(
|
||||||
constants.MaxBlockVersion,
|
version,
|
||||||
parents,
|
parents,
|
||||||
hashMerkleRoot,
|
hashMerkleRoot,
|
||||||
acceptedIDMerkleRoot,
|
acceptedIDMerkleRoot,
|
||||||
|
@ -94,9 +94,14 @@ func (bb *testBlockBuilder) buildUTXOInvalidHeader(stagingArea *model.StagingAre
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version := constants.BlockVersionBeforeHF1
|
||||||
|
if daaScore >= bb.hf1DAAScore {
|
||||||
|
version = constants.BlockVersionAfterHF1
|
||||||
|
}
|
||||||
|
|
||||||
bb.nonceCounter++
|
bb.nonceCounter++
|
||||||
return blockheader.NewImmutableBlockHeader(
|
return blockheader.NewImmutableBlockHeader(
|
||||||
constants.MaxBlockVersion,
|
version,
|
||||||
parents,
|
parents,
|
||||||
hashMerkleRoot,
|
hashMerkleRoot,
|
||||||
&externalapi.DomainHash{},
|
&externalapi.DomainHash{},
|
||||||
@ -283,3 +288,7 @@ func (bb *testBlockBuilder) BuildUTXOInvalidBlock(parentHashes []*externalapi.Do
|
|||||||
Transactions: transactions,
|
Transactions: transactions,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bb *testBlockBuilder) SetNonceCounter(nonceCounter uint64) {
|
||||||
|
bb.nonceCounter = nonceCounter
|
||||||
|
}
|
||||||
|
@ -28,6 +28,7 @@ type blockProcessor struct {
|
|||||||
coinbaseManager model.CoinbaseManager
|
coinbaseManager model.CoinbaseManager
|
||||||
headerTipsManager model.HeadersSelectedTipManager
|
headerTipsManager model.HeadersSelectedTipManager
|
||||||
syncManager model.SyncManager
|
syncManager model.SyncManager
|
||||||
|
finalityManager model.FinalityManager
|
||||||
|
|
||||||
acceptanceDataStore model.AcceptanceDataStore
|
acceptanceDataStore model.AcceptanceDataStore
|
||||||
blockStore model.BlockStore
|
blockStore model.BlockStore
|
||||||
|
@ -140,7 +140,7 @@ func (v *blockValidator) checkBlockTransactionOrder(block *externalapi.DomainBlo
|
|||||||
|
|
||||||
func (v *blockValidator) checkTransactionsInIsolation(block *externalapi.DomainBlock) error {
|
func (v *blockValidator) checkTransactionsInIsolation(block *externalapi.DomainBlock) error {
|
||||||
for _, tx := range block.Transactions {
|
for _, tx := range block.Transactions {
|
||||||
err := v.transactionValidator.ValidateTransactionInIsolation(tx)
|
err := v.transactionValidator.ValidateTransactionInIsolation(tx, block.Header.DAAScore())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "transaction %s failed isolation "+
|
return errors.Wrapf(err, "transaction %s failed isolation "+
|
||||||
"check", consensushashing.TransactionID(tx))
|
"check", consensushashing.TransactionID(tx))
|
||||||
@ -220,7 +220,7 @@ func (v *blockValidator) validateGasLimit(block *externalapi.DomainBlock) error
|
|||||||
|
|
||||||
func (v *blockValidator) checkBlockMass(block *externalapi.DomainBlock) error {
|
func (v *blockValidator) checkBlockMass(block *externalapi.DomainBlock) error {
|
||||||
mass := uint64(0)
|
mass := uint64(0)
|
||||||
if !v.ignoreHeaderMass {
|
if block.Header.DAAScore() < v.hf1DAAScore {
|
||||||
mass += v.headerEstimatedSerializedSize(block.Header)
|
mass += v.headerEstimatedSerializedSize(block.Header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1307,7 +1307,7 @@ func initBlockWithFirstTransactionDifferentThanCoinbase(consensusConfig *consens
|
|||||||
|
|
||||||
return &externalapi.DomainBlock{
|
return &externalapi.DomainBlock{
|
||||||
Header: blockheader.NewImmutableBlockHeader(
|
Header: blockheader.NewImmutableBlockHeader(
|
||||||
constants.MaxBlockVersion,
|
constants.BlockVersionBeforeHF1,
|
||||||
[]externalapi.BlockLevelParents{[]*externalapi.DomainHash{consensusConfig.GenesisHash}},
|
[]externalapi.BlockLevelParents{[]*externalapi.DomainHash{consensusConfig.GenesisHash}},
|
||||||
merkle.CalculateHashMerkleRoot([]*externalapi.DomainTransaction{tx}),
|
merkle.CalculateHashMerkleRoot([]*externalapi.DomainTransaction{tx}),
|
||||||
&externalapi.DomainHash{},
|
&externalapi.DomainHash{},
|
||||||
|
@ -2,6 +2,7 @@ package blockvalidator_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -107,9 +108,14 @@ func TestCheckParentsIncest(t *testing.T) {
|
|||||||
t.Fatalf("AddBlock: %+v", err)
|
t.Fatalf("AddBlock: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version := constants.BlockVersionBeforeHF1
|
||||||
|
if consensusConfig.HF1DAAScore == 0 {
|
||||||
|
version = constants.BlockVersionAfterHF1
|
||||||
|
}
|
||||||
|
|
||||||
directParentsRelationBlock := &externalapi.DomainBlock{
|
directParentsRelationBlock := &externalapi.DomainBlock{
|
||||||
Header: blockheader.NewImmutableBlockHeader(
|
Header: blockheader.NewImmutableBlockHeader(
|
||||||
0,
|
version,
|
||||||
[]externalapi.BlockLevelParents{[]*externalapi.DomainHash{a, b}},
|
[]externalapi.BlockLevelParents{[]*externalapi.DomainHash{a, b}},
|
||||||
&externalapi.DomainHash{},
|
&externalapi.DomainHash{},
|
||||||
&externalapi.DomainHash{},
|
&externalapi.DomainHash{},
|
||||||
@ -132,7 +138,7 @@ func TestCheckParentsIncest(t *testing.T) {
|
|||||||
|
|
||||||
indirectParentsRelationBlock := &externalapi.DomainBlock{
|
indirectParentsRelationBlock := &externalapi.DomainBlock{
|
||||||
Header: blockheader.NewImmutableBlockHeader(
|
Header: blockheader.NewImmutableBlockHeader(
|
||||||
0,
|
version,
|
||||||
[]externalapi.BlockLevelParents{[]*externalapi.DomainHash{consensusConfig.GenesisHash, b}},
|
[]externalapi.BlockLevelParents{[]*externalapi.DomainHash{consensusConfig.GenesisHash, b}},
|
||||||
&externalapi.DomainHash{},
|
&externalapi.DomainHash{},
|
||||||
&externalapi.DomainHash{},
|
&externalapi.DomainHash{},
|
||||||
|
@ -22,9 +22,11 @@ func (v *blockValidator) ValidateHeaderInIsolation(stagingArea *model.StagingAre
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.checkBlockVersion(header)
|
if !blockHash.Equal(v.genesisHash) {
|
||||||
if err != nil {
|
err = v.checkBlockVersion(header)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.checkBlockTimestampInIsolation(header)
|
err = v.checkBlockTimestampInIsolation(header)
|
||||||
@ -54,9 +56,16 @@ func (v *blockValidator) checkParentsLimit(header externalapi.BlockHeader) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *blockValidator) checkBlockVersion(header externalapi.BlockHeader) error {
|
func (v *blockValidator) checkBlockVersion(header externalapi.BlockHeader) error {
|
||||||
if header.Version() > constants.MaxBlockVersion {
|
if header.DAAScore() >= v.hf1DAAScore {
|
||||||
return errors.Wrapf(
|
if header.Version() != constants.BlockVersionAfterHF1 {
|
||||||
ruleerrors.ErrBlockVersionIsUnknown, "The block version is unknown.")
|
return errors.Wrapf(
|
||||||
|
ruleerrors.ErrWrongBlockVersion, "After HF1 the block version should be %d", constants.BlockVersionAfterHF1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if header.Version() != constants.BlockVersionBeforeHF1 {
|
||||||
|
return errors.Wrapf(
|
||||||
|
ruleerrors.ErrWrongBlockVersion, "Beofre HF1 the block version should be %d", constants.BlockVersionBeforeHF1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -62,8 +62,13 @@ func CheckBlockVersion(t *testing.T, tc testapi.TestConsensus, consensusConfig *
|
|||||||
t.Fatalf("BuildBlockWithParents: %+v", err)
|
t.Fatalf("BuildBlockWithParents: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectedVersion := constants.BlockVersionBeforeHF1
|
||||||
|
if consensusConfig.HF1DAAScore == 0 {
|
||||||
|
expectedVersion = constants.BlockVersionAfterHF1
|
||||||
|
}
|
||||||
|
|
||||||
block.Header = blockheader.NewImmutableBlockHeader(
|
block.Header = blockheader.NewImmutableBlockHeader(
|
||||||
constants.MaxBlockVersion+1,
|
expectedVersion+1,
|
||||||
block.Header.Parents(),
|
block.Header.Parents(),
|
||||||
block.Header.HashMerkleRoot(),
|
block.Header.HashMerkleRoot(),
|
||||||
block.Header.AcceptedIDMerkleRoot(),
|
block.Header.AcceptedIDMerkleRoot(),
|
||||||
@ -78,7 +83,7 @@ func CheckBlockVersion(t *testing.T, tc testapi.TestConsensus, consensusConfig *
|
|||||||
)
|
)
|
||||||
|
|
||||||
_, err = tc.ValidateAndInsertBlock(block, true)
|
_, err = tc.ValidateAndInsertBlock(block, true)
|
||||||
if !errors.Is(err, ruleerrors.ErrBlockVersionIsUnknown) {
|
if !errors.Is(err, ruleerrors.ErrWrongBlockVersion) {
|
||||||
t.Fatalf("Unexpected error: %+v", err)
|
t.Fatalf("Unexpected error: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ type blockValidator struct {
|
|||||||
maxBlockParents externalapi.KType
|
maxBlockParents externalapi.KType
|
||||||
timestampDeviationTolerance int
|
timestampDeviationTolerance int
|
||||||
targetTimePerBlock time.Duration
|
targetTimePerBlock time.Duration
|
||||||
ignoreHeaderMass bool
|
hf1DAAScore uint64
|
||||||
maxBlockLevel int
|
maxBlockLevel int
|
||||||
|
|
||||||
databaseContext model.DBReader
|
databaseContext model.DBReader
|
||||||
@ -64,7 +64,7 @@ func New(powMax *big.Int,
|
|||||||
maxBlockParents externalapi.KType,
|
maxBlockParents externalapi.KType,
|
||||||
timestampDeviationTolerance int,
|
timestampDeviationTolerance int,
|
||||||
targetTimePerBlock time.Duration,
|
targetTimePerBlock time.Duration,
|
||||||
ignoreHeaderMass bool,
|
hf1DAAScore uint64,
|
||||||
maxBlockLevel int,
|
maxBlockLevel int,
|
||||||
|
|
||||||
databaseContext model.DBReader,
|
databaseContext model.DBReader,
|
||||||
@ -104,7 +104,7 @@ func New(powMax *big.Int,
|
|||||||
maxBlockMass: maxBlockMass,
|
maxBlockMass: maxBlockMass,
|
||||||
mergeSetSizeLimit: mergeSetSizeLimit,
|
mergeSetSizeLimit: mergeSetSizeLimit,
|
||||||
maxBlockParents: maxBlockParents,
|
maxBlockParents: maxBlockParents,
|
||||||
ignoreHeaderMass: ignoreHeaderMass,
|
hf1DAAScore: hf1DAAScore,
|
||||||
maxBlockLevel: maxBlockLevel,
|
maxBlockLevel: maxBlockLevel,
|
||||||
|
|
||||||
timestampDeviationTolerance: timestampDeviationTolerance,
|
timestampDeviationTolerance: timestampDeviationTolerance,
|
||||||
|
@ -282,19 +282,19 @@ func (csm *consensusStateManager) boundedMergeBreakingParents(stagingArea *model
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtualMergeDepthRoot, err := csm.mergeDepthManager.VirtualMergeDepthRoot(stagingArea)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("The merge depth root of virtual is: %s", virtualMergeDepthRoot)
|
||||||
|
|
||||||
potentiallyKosherizingBlocks, err :=
|
potentiallyKosherizingBlocks, err :=
|
||||||
csm.mergeDepthManager.NonBoundedMergeDepthViolatingBlues(stagingArea, model.VirtualBlockHash, false)
|
csm.mergeDepthManager.NonBoundedMergeDepthViolatingBlues(stagingArea, model.VirtualBlockHash, virtualMergeDepthRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debugf("The potentially kosherizing blocks are: %s", potentiallyKosherizingBlocks)
|
log.Debugf("The potentially kosherizing blocks are: %s", potentiallyKosherizingBlocks)
|
||||||
|
|
||||||
virtualFinalityPoint, err := csm.finalityManager.VirtualFinalityPoint(stagingArea)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("The finality point of the virtual is: %s", virtualFinalityPoint)
|
|
||||||
|
|
||||||
var badReds []*externalapi.DomainHash
|
var badReds []*externalapi.DomainHash
|
||||||
|
|
||||||
virtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, model.VirtualBlockHash, false)
|
virtualGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, stagingArea, model.VirtualBlockHash, false)
|
||||||
@ -303,13 +303,13 @@ func (csm *consensusStateManager) boundedMergeBreakingParents(stagingArea *model
|
|||||||
}
|
}
|
||||||
for _, redBlock := range virtualGHOSTDAGData.MergeSetReds() {
|
for _, redBlock := range virtualGHOSTDAGData.MergeSetReds() {
|
||||||
log.Debugf("Check whether red block %s is kosherized", redBlock)
|
log.Debugf("Check whether red block %s is kosherized", redBlock)
|
||||||
isFinalityPointInPast, err := csm.dagTopologyManager.IsAncestorOf(stagingArea, virtualFinalityPoint, redBlock)
|
isMergeDepthRootInPast, err := csm.dagTopologyManager.IsAncestorOf(stagingArea, virtualMergeDepthRoot, redBlock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if isFinalityPointInPast {
|
if isMergeDepthRootInPast {
|
||||||
log.Debugf("Skipping red block %s because it has the virtual's"+
|
log.Debugf("Skipping red block %s because it has the virtual's"+
|
||||||
" finality point in its past", redBlock)
|
" merge depth root in its past", redBlock)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,12 +185,12 @@ func TestBlockWindow(t *testing.T) {
|
|||||||
{
|
{
|
||||||
parents: []string{"C", "D"},
|
parents: []string{"C", "D"},
|
||||||
id: "E",
|
id: "E",
|
||||||
expectedWindow: []string{"D", "C", "B"},
|
expectedWindow: []string{"C", "D", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"C", "D"},
|
parents: []string{"C", "D"},
|
||||||
id: "F",
|
id: "F",
|
||||||
expectedWindow: []string{"D", "C", "B"},
|
expectedWindow: []string{"C", "D", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"A"},
|
parents: []string{"A"},
|
||||||
@ -205,37 +205,37 @@ func TestBlockWindow(t *testing.T) {
|
|||||||
{
|
{
|
||||||
parents: []string{"H", "F"},
|
parents: []string{"H", "F"},
|
||||||
id: "I",
|
id: "I",
|
||||||
expectedWindow: []string{"F", "D", "H", "C", "G", "B"},
|
expectedWindow: []string{"F", "C", "D", "H", "G", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"I"},
|
parents: []string{"I"},
|
||||||
id: "J",
|
id: "J",
|
||||||
expectedWindow: []string{"I", "F", "D", "H", "C", "G", "B"},
|
expectedWindow: []string{"I", "F", "C", "D", "H", "G", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"J"},
|
parents: []string{"J"},
|
||||||
id: "K",
|
id: "K",
|
||||||
expectedWindow: []string{"J", "I", "F", "D", "H", "C", "G", "B"},
|
expectedWindow: []string{"J", "I", "F", "C", "D", "H", "G", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"K"},
|
parents: []string{"K"},
|
||||||
id: "L",
|
id: "L",
|
||||||
expectedWindow: []string{"K", "J", "I", "F", "D", "H", "C", "G", "B"},
|
expectedWindow: []string{"K", "J", "I", "F", "C", "D", "H", "G", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"L"},
|
parents: []string{"L"},
|
||||||
id: "M",
|
id: "M",
|
||||||
expectedWindow: []string{"L", "K", "J", "I", "F", "D", "H", "C", "G", "B"},
|
expectedWindow: []string{"L", "K", "J", "I", "F", "C", "D", "H", "G", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"M"},
|
parents: []string{"M"},
|
||||||
id: "N",
|
id: "N",
|
||||||
expectedWindow: []string{"M", "L", "K", "J", "I", "F", "D", "H", "C", "G"},
|
expectedWindow: []string{"M", "L", "K", "J", "I", "F", "C", "D", "H", "G"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"N"},
|
parents: []string{"N"},
|
||||||
id: "O",
|
id: "O",
|
||||||
expectedWindow: []string{"N", "M", "L", "K", "J", "I", "F", "D", "H", "C"},
|
expectedWindow: []string{"N", "M", "L", "K", "J", "I", "F", "C", "D", "H"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dagconfig.SimnetParams.Name: {
|
dagconfig.SimnetParams.Name: {
|
||||||
@ -255,14 +255,14 @@ func TestBlockWindow(t *testing.T) {
|
|||||||
expectedWindow: []string{"B"},
|
expectedWindow: []string{"B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"D", "C"},
|
parents: []string{"C", "D"},
|
||||||
id: "E",
|
id: "E",
|
||||||
expectedWindow: []string{"D", "C", "B"},
|
expectedWindow: []string{"C", "D", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"D", "C"},
|
parents: []string{"C", "D"},
|
||||||
id: "F",
|
id: "F",
|
||||||
expectedWindow: []string{"D", "C", "B"},
|
expectedWindow: []string{"C", "D", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"A"},
|
parents: []string{"A"},
|
||||||
@ -277,37 +277,37 @@ func TestBlockWindow(t *testing.T) {
|
|||||||
{
|
{
|
||||||
parents: []string{"H", "F"},
|
parents: []string{"H", "F"},
|
||||||
id: "I",
|
id: "I",
|
||||||
expectedWindow: []string{"F", "H", "D", "C", "B", "G"},
|
expectedWindow: []string{"F", "C", "H", "D", "B", "G"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"I"},
|
parents: []string{"I"},
|
||||||
id: "J",
|
id: "J",
|
||||||
expectedWindow: []string{"I", "F", "H", "D", "C", "B", "G"},
|
expectedWindow: []string{"I", "F", "C", "H", "D", "B", "G"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"J"},
|
parents: []string{"J"},
|
||||||
id: "K",
|
id: "K",
|
||||||
expectedWindow: []string{"J", "I", "F", "H", "D", "C", "B", "G"},
|
expectedWindow: []string{"J", "I", "F", "C", "H", "D", "B", "G"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"K"},
|
parents: []string{"K"},
|
||||||
id: "L",
|
id: "L",
|
||||||
expectedWindow: []string{"K", "J", "I", "F", "H", "D", "C", "B", "G"},
|
expectedWindow: []string{"K", "J", "I", "F", "C", "H", "D", "B", "G"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"L"},
|
parents: []string{"L"},
|
||||||
id: "M",
|
id: "M",
|
||||||
expectedWindow: []string{"L", "K", "J", "I", "F", "H", "D", "C", "B", "G"},
|
expectedWindow: []string{"L", "K", "J", "I", "F", "C", "H", "D", "B", "G"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"M"},
|
parents: []string{"M"},
|
||||||
id: "N",
|
id: "N",
|
||||||
expectedWindow: []string{"M", "L", "K", "J", "I", "F", "H", "D", "C", "B"},
|
expectedWindow: []string{"M", "L", "K", "J", "I", "F", "C", "H", "D", "B"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parents: []string{"N"},
|
parents: []string{"N"},
|
||||||
id: "O",
|
id: "O",
|
||||||
expectedWindow: []string{"N", "M", "L", "K", "J", "I", "F", "H", "D", "C"},
|
expectedWindow: []string{"N", "M", "L", "K", "J", "I", "F", "C", "H", "D"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ func TestGHOSTDAG(t *testing.T) {
|
|||||||
blockID := StringToDomainHash(testBlockData.ID)
|
blockID := StringToDomainHash(testBlockData.ID)
|
||||||
dagTopology.parentsMap[*blockID] = StringToDomainHashSlice(testBlockData.Parents)
|
dagTopology.parentsMap[*blockID] = StringToDomainHashSlice(testBlockData.Parents)
|
||||||
blockHeadersStore.dagMap[*blockID] = blockheader.NewImmutableBlockHeader(
|
blockHeadersStore.dagMap[*blockID] = blockheader.NewImmutableBlockHeader(
|
||||||
constants.MaxBlockVersion,
|
constants.BlockVersionBeforeHF1,
|
||||||
[]externalapi.BlockLevelParents{StringToDomainHashSlice(testBlockData.Parents)},
|
[]externalapi.BlockLevelParents{StringToDomainHashSlice(testBlockData.Parents)},
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
7
domain/consensus/processes/mergedepthmanager/log.go
Normal file
7
domain/consensus/processes/mergedepthmanager/log.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package mergedepthmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logger.RegisterSubSystem("MDMN")
|
@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
|
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,7 +14,15 @@ type mergeDepthManager struct {
|
|||||||
dagTraversalManager model.DAGTraversalManager
|
dagTraversalManager model.DAGTraversalManager
|
||||||
finalityManager model.FinalityManager
|
finalityManager model.FinalityManager
|
||||||
|
|
||||||
ghostdagDataStore model.GHOSTDAGDataStore
|
genesisHash *externalapi.DomainHash
|
||||||
|
mergeDepth uint64
|
||||||
|
hf1DAAScore uint64
|
||||||
|
|
||||||
|
ghostdagDataStore model.GHOSTDAGDataStore
|
||||||
|
mergeDepthRootStore model.MergeDepthRootStore
|
||||||
|
daaBlocksStore model.DAABlocksStore
|
||||||
|
pruningStore model.PruningStore
|
||||||
|
finalityStore model.FinalityStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instantiates a new MergeDepthManager
|
// New instantiates a new MergeDepthManager
|
||||||
@ -22,24 +31,36 @@ func New(
|
|||||||
dagTopologyManager model.DAGTopologyManager,
|
dagTopologyManager model.DAGTopologyManager,
|
||||||
dagTraversalManager model.DAGTraversalManager,
|
dagTraversalManager model.DAGTraversalManager,
|
||||||
finalityManager model.FinalityManager,
|
finalityManager model.FinalityManager,
|
||||||
ghostdagDataStore model.GHOSTDAGDataStore) model.MergeDepthManager {
|
|
||||||
|
genesisHash *externalapi.DomainHash,
|
||||||
|
mergeDepth uint64,
|
||||||
|
hf1DAAScore uint64,
|
||||||
|
|
||||||
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
||||||
|
mergeDepthRootStore model.MergeDepthRootStore,
|
||||||
|
daaBlocksStore model.DAABlocksStore,
|
||||||
|
pruningStore model.PruningStore,
|
||||||
|
finalityStore model.FinalityStore) model.MergeDepthManager {
|
||||||
|
|
||||||
return &mergeDepthManager{
|
return &mergeDepthManager{
|
||||||
databaseContext: databaseContext,
|
databaseContext: databaseContext,
|
||||||
dagTopologyManager: dagTopologyManager,
|
dagTopologyManager: dagTopologyManager,
|
||||||
dagTraversalManager: dagTraversalManager,
|
dagTraversalManager: dagTraversalManager,
|
||||||
finalityManager: finalityManager,
|
finalityManager: finalityManager,
|
||||||
|
genesisHash: genesisHash,
|
||||||
|
mergeDepth: mergeDepth,
|
||||||
|
hf1DAAScore: hf1DAAScore,
|
||||||
ghostdagDataStore: ghostdagDataStore,
|
ghostdagDataStore: ghostdagDataStore,
|
||||||
|
mergeDepthRootStore: mergeDepthRootStore,
|
||||||
|
daaBlocksStore: daaBlocksStore,
|
||||||
|
pruningStore: pruningStore,
|
||||||
|
finalityStore: finalityStore,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckBoundedMergeDepth is used for validation, so must follow the HF1 DAA score for determining the correct depth to verify
|
||||||
func (mdm *mergeDepthManager) CheckBoundedMergeDepth(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) error {
|
func (mdm *mergeDepthManager) CheckBoundedMergeDepth(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) error {
|
||||||
nonBoundedMergeDepthViolatingBlues, err := mdm.NonBoundedMergeDepthViolatingBlues(stagingArea, blockHash, isBlockWithTrustedData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ghostdagData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, blockHash, false)
|
ghostdagData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, blockHash, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -50,28 +71,34 @@ func (mdm *mergeDepthManager) CheckBoundedMergeDepth(stagingArea *model.StagingA
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
finalityPoint, err := mdm.finalityManager.FinalityPoint(stagingArea, blockHash, isBlockWithTrustedData)
|
// For validation, we must follow the HF1 DAA score in order to determine the correct depth to verify
|
||||||
|
mergeDepthRootByHF1, err := mdm.mergeDepthRootByHF1DAAScoreForValidationOnly(stagingArea, blockHash, isBlockWithTrustedData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonBoundedMergeDepthViolatingBlues, err := mdm.NonBoundedMergeDepthViolatingBlues(stagingArea, blockHash, mergeDepthRootByHF1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, red := range ghostdagData.MergeSetReds() {
|
for _, red := range ghostdagData.MergeSetReds() {
|
||||||
doesRedHaveFinalityPointInPast, err := mdm.dagTopologyManager.IsAncestorOf(stagingArea, finalityPoint, red)
|
doesRedHaveMergeRootInPast, err := mdm.dagTopologyManager.IsAncestorOf(stagingArea, mergeDepthRootByHF1, red)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if doesRedHaveFinalityPointInPast {
|
if doesRedHaveMergeRootInPast {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
isRedInPastOfAnyNonFinalityViolatingBlue, err :=
|
isRedInPastOfAnyNonMergeDepthViolatingBlue, err :=
|
||||||
mdm.dagTopologyManager.IsAncestorOfAny(stagingArea, red, nonBoundedMergeDepthViolatingBlues)
|
mdm.dagTopologyManager.IsAncestorOfAny(stagingArea, red, nonBoundedMergeDepthViolatingBlues)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isRedInPastOfAnyNonFinalityViolatingBlue {
|
if !isRedInPastOfAnyNonMergeDepthViolatingBlue {
|
||||||
return errors.Wrapf(ruleerrors.ErrViolatingBoundedMergeDepth, "block is violating bounded merge depth")
|
return errors.Wrapf(ruleerrors.ErrViolatingBoundedMergeDepth, "block is violating bounded merge depth")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,7 +106,9 @@ func (mdm *mergeDepthManager) CheckBoundedMergeDepth(stagingArea *model.StagingA
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mdm *mergeDepthManager) NonBoundedMergeDepthViolatingBlues(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) ([]*externalapi.DomainHash, error) {
|
func (mdm *mergeDepthManager) NonBoundedMergeDepthViolatingBlues(
|
||||||
|
stagingArea *model.StagingArea, blockHash, mergeDepthRoot *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||||
|
|
||||||
ghostdagData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, blockHash, false)
|
ghostdagData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, blockHash, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -87,20 +116,173 @@ func (mdm *mergeDepthManager) NonBoundedMergeDepthViolatingBlues(stagingArea *mo
|
|||||||
|
|
||||||
nonBoundedMergeDepthViolatingBlues := make([]*externalapi.DomainHash, 0, len(ghostdagData.MergeSetBlues()))
|
nonBoundedMergeDepthViolatingBlues := make([]*externalapi.DomainHash, 0, len(ghostdagData.MergeSetBlues()))
|
||||||
|
|
||||||
finalityPoint, err := mdm.finalityManager.FinalityPoint(stagingArea, blockHash, isBlockWithTrustedData)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, blue := range ghostdagData.MergeSetBlues() {
|
for _, blue := range ghostdagData.MergeSetBlues() {
|
||||||
notViolatingFinality, err := mdm.dagTopologyManager.IsInSelectedParentChainOf(stagingArea, finalityPoint, blue)
|
isMergeDepthRootInSelectedChainOfBlue, err := mdm.dagTopologyManager.IsInSelectedParentChainOf(stagingArea, mergeDepthRoot, blue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if notViolatingFinality {
|
if isMergeDepthRootInSelectedChainOfBlue {
|
||||||
nonBoundedMergeDepthViolatingBlues = append(nonBoundedMergeDepthViolatingBlues, blue)
|
nonBoundedMergeDepthViolatingBlues = append(nonBoundedMergeDepthViolatingBlues, blue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nonBoundedMergeDepthViolatingBlues, nil
|
return nonBoundedMergeDepthViolatingBlues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mdm *mergeDepthManager) mergeDepthRootByHF1DAAScoreForValidationOnly(
|
||||||
|
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) (*externalapi.DomainHash, error) {
|
||||||
|
daaScore, err := mdm.daaBlocksStore.DAAScore(mdm.databaseContext, stagingArea, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We call both, merge depth root and finality, in order to trigger storage persistency for both,
|
||||||
|
// although only one of them is used below
|
||||||
|
mergeDepthRoot, err := mdm.MergeDepthRoot(stagingArea, blockHash, isBlockWithTrustedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// As noted above, this line should not be removed following the HF, unless we validate that storage of
|
||||||
|
// finality point is not needed any more
|
||||||
|
finalityPoint, err := mdm.finalityManager.FinalityPoint(stagingArea, blockHash, isBlockWithTrustedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if daaScore >= mdm.hf1DAAScore {
|
||||||
|
return mergeDepthRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fall back to the merge depth root before the HF, which was the finality point
|
||||||
|
return finalityPoint, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdm *mergeDepthManager) VirtualMergeDepthRoot(stagingArea *model.StagingArea) (*externalapi.DomainHash, error) {
|
||||||
|
log.Tracef("VirtualMergeDepthRoot start")
|
||||||
|
defer log.Tracef("VirtualMergeDepthRoot end")
|
||||||
|
|
||||||
|
virtualMergeDepthRoot, err := mdm.calculateMergeDepthRoot(stagingArea, model.VirtualBlockHash, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugf("The current virtual merge depth root is: %s", virtualMergeDepthRoot)
|
||||||
|
|
||||||
|
return virtualMergeDepthRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdm *mergeDepthManager) MergeDepthRoot(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) (*externalapi.DomainHash, error) {
|
||||||
|
log.Tracef("MergeDepthRoot start")
|
||||||
|
defer log.Tracef("MergeDepthRoot end")
|
||||||
|
if blockHash.Equal(model.VirtualBlockHash) {
|
||||||
|
return mdm.VirtualMergeDepthRoot(stagingArea)
|
||||||
|
}
|
||||||
|
root, err := mdm.mergeDepthRootStore.MergeDepthRoot(mdm.databaseContext, stagingArea, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("%s merge depth root not found in store - calculating", blockHash)
|
||||||
|
if errors.Is(err, database.ErrNotFound) {
|
||||||
|
return mdm.calculateAndStageMergeDepthRoot(stagingArea, blockHash, isBlockWithTrustedData)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdm *mergeDepthManager) calculateAndStageMergeDepthRoot(
|
||||||
|
stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) (*externalapi.DomainHash, error) {
|
||||||
|
|
||||||
|
root, err := mdm.calculateMergeDepthRoot(stagingArea, blockHash, isBlockWithTrustedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mdm.mergeDepthRootStore.StageMergeDepthRoot(stagingArea, blockHash, root)
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mdm *mergeDepthManager) calculateMergeDepthRoot(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, isBlockWithTrustedData bool) (
|
||||||
|
*externalapi.DomainHash, error) {
|
||||||
|
|
||||||
|
log.Tracef("calculateMergeDepthRoot start")
|
||||||
|
defer log.Tracef("calculateMergeDepthRoot end")
|
||||||
|
|
||||||
|
if isBlockWithTrustedData {
|
||||||
|
return model.VirtualGenesisBlockHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ghostdagData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, blockHash, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ghostdagData.BlueScore() < mdm.mergeDepth {
|
||||||
|
log.Debugf("%s blue score lower then merge depth - returning genesis as merge depth root", blockHash)
|
||||||
|
return mdm.genesisHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pruningPoint, err := mdm.pruningStore.PruningPoint(mdm.databaseContext, stagingArea)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pruningPointGhostdagData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, pruningPoint, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ghostdagData.BlueScore() < pruningPointGhostdagData.BlueScore()+mdm.mergeDepth {
|
||||||
|
log.Debugf("%s blue score less than merge depth over pruning point - returning virtual genesis as merge depth root", blockHash)
|
||||||
|
return model.VirtualGenesisBlockHash, nil
|
||||||
|
}
|
||||||
|
isPruningPointOnChain, err := mdm.dagTopologyManager.IsInSelectedParentChainOf(stagingArea, pruningPoint, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !isPruningPointOnChain {
|
||||||
|
log.Debugf("pruning point not in selected chain of %s - returning virtual genesis as merge depth root", blockHash)
|
||||||
|
return model.VirtualGenesisBlockHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedParent := ghostdagData.SelectedParent()
|
||||||
|
if selectedParent.Equal(mdm.genesisHash) {
|
||||||
|
return mdm.genesisHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current, err := mdm.mergeDepthRootStore.MergeDepthRoot(mdm.databaseContext, stagingArea, ghostdagData.SelectedParent())
|
||||||
|
if database.IsNotFoundError(err) {
|
||||||
|
// This should only occur for a few blocks following the upgrade
|
||||||
|
log.Debugf("merge point root not in store for %s, falling back to finality point", ghostdagData.SelectedParent())
|
||||||
|
current, err = mdm.finalityStore.FinalityPoint(mdm.databaseContext, stagingArea, ghostdagData.SelectedParent())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// In this case we expect the pruning point or a block above it to be the merge depth root.
|
||||||
|
// Note that above we already verified the chain and distance conditions for this
|
||||||
|
if current.Equal(model.VirtualGenesisBlockHash) {
|
||||||
|
current = pruningPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredBlueScore := ghostdagData.BlueScore() - mdm.mergeDepth
|
||||||
|
log.Debugf("%s's merge depth root is the one having the highest blue score lower then %d", blockHash, requiredBlueScore)
|
||||||
|
|
||||||
|
var next *externalapi.DomainHash
|
||||||
|
for {
|
||||||
|
next, err = mdm.dagTopologyManager.ChildInSelectedParentChainOf(stagingArea, current, blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nextGHOSTDAGData, err := mdm.ghostdagDataStore.Get(mdm.databaseContext, stagingArea, next, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if nextGHOSTDAGData.BlueScore() >= requiredBlueScore {
|
||||||
|
log.Debugf("%s's merge depth root is %s", blockHash, current)
|
||||||
|
return current, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
current = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -38,7 +38,7 @@ func TestPruning(t *testing.T) {
|
|||||||
"dag-for-test-pruning.json": {
|
"dag-for-test-pruning.json": {
|
||||||
dagconfig.MainnetParams.Name: "503",
|
dagconfig.MainnetParams.Name: "503",
|
||||||
dagconfig.TestnetParams.Name: "502",
|
dagconfig.TestnetParams.Name: "502",
|
||||||
dagconfig.DevnetParams.Name: "502",
|
dagconfig.DevnetParams.Name: "503",
|
||||||
dagconfig.SimnetParams.Name: "503",
|
dagconfig.SimnetParams.Name: "503",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,14 @@ func (v *transactionValidator) ValidateTransactionInContextIgnoringUTXO(stagingA
|
|||||||
return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "unfinalized transaction %v", tx)
|
return errors.Wrapf(ruleerrors.ErrUnfinalizedTx, "unfinalized transaction %v", tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove checkTransactionAmountRanges from here once HF was activated. It's only temporary
|
||||||
|
// because in the HF transition period checkTransactionAmountRanges is not context free.
|
||||||
|
isHF1Activated := povBlockDAAScore >= v.hf1DAAScore
|
||||||
|
err = v.checkTransactionAmountRanges(tx, isHF1Activated)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +81,13 @@ func (v *transactionValidator) ValidateTransactionInContextAndPopulateFee(stagin
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSompiIn, err := v.checkTransactionInputAmounts(tx)
|
daaScore, err := v.daaBlocksStore.DAAScore(v.databaseContext, stagingArea, povBlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isHF1Activated := daaScore >= v.hf1DAAScore
|
||||||
|
totalSompiIn, err := v.checkTransactionInputAmounts(tx, isHF1Activated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -135,7 +149,7 @@ func (v *transactionValidator) checkTransactionCoinbaseMaturity(stagingArea *mod
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *transactionValidator) checkTransactionInputAmounts(tx *externalapi.DomainTransaction) (totalSompiIn uint64, err error) {
|
func (v *transactionValidator) checkTransactionInputAmounts(tx *externalapi.DomainTransaction, isHF1Activated bool) (totalSompiIn uint64, err error) {
|
||||||
totalSompiIn = 0
|
totalSompiIn = 0
|
||||||
|
|
||||||
var missingOutpoints []*externalapi.DomainOutpoint
|
var missingOutpoints []*externalapi.DomainOutpoint
|
||||||
@ -152,7 +166,7 @@ func (v *transactionValidator) checkTransactionInputAmounts(tx *externalapi.Doma
|
|||||||
// a transaction are in a unit value known as a sompi. One
|
// a transaction are in a unit value known as a sompi. One
|
||||||
// kaspa is a quantity of sompi as defined by the
|
// kaspa is a quantity of sompi as defined by the
|
||||||
// SompiPerKaspa constant.
|
// SompiPerKaspa constant.
|
||||||
totalSompiIn, err = v.checkEntryAmounts(utxoEntry, totalSompiIn)
|
totalSompiIn, err = v.checkEntryAmounts(utxoEntry, totalSompiIn, isHF1Activated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -165,18 +179,24 @@ func (v *transactionValidator) checkTransactionInputAmounts(tx *externalapi.Doma
|
|||||||
return totalSompiIn, nil
|
return totalSompiIn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *transactionValidator) checkEntryAmounts(entry externalapi.UTXOEntry, totalSompiInBefore uint64) (totalSompiInAfter uint64, err error) {
|
func (v *transactionValidator) checkEntryAmounts(entry externalapi.UTXOEntry, totalSompiInBefore uint64, isHF1Activated bool) (totalSompiInAfter uint64, err error) {
|
||||||
// The total of all outputs must not be more than the max
|
// The total of all outputs must not be more than the max
|
||||||
// allowed per transaction. Also, we could potentially overflow
|
// allowed per transaction. Also, we could potentially overflow
|
||||||
// the accumulator so check for overflow.
|
// the accumulator so check for overflow.
|
||||||
|
|
||||||
|
maxSompi := constants.MaxSompiBeforeHF1
|
||||||
|
if isHF1Activated {
|
||||||
|
maxSompi = constants.MaxSompiAfterHF1
|
||||||
|
}
|
||||||
|
|
||||||
originTxSompi := entry.Amount()
|
originTxSompi := entry.Amount()
|
||||||
totalSompiInAfter = totalSompiInBefore + originTxSompi
|
totalSompiInAfter = totalSompiInBefore + originTxSompi
|
||||||
if totalSompiInAfter < totalSompiInBefore ||
|
if totalSompiInAfter < totalSompiInBefore ||
|
||||||
totalSompiInAfter > constants.MaxSompi {
|
totalSompiInAfter > maxSompi {
|
||||||
return 0, errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
|
return 0, errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
|
||||||
"inputs is %d which is higher than max "+
|
"inputs is %d which is higher than max "+
|
||||||
"allowed value of %d", totalSompiInBefore,
|
"allowed value of %d", totalSompiInBefore,
|
||||||
constants.MaxSompi)
|
maxSompi)
|
||||||
}
|
}
|
||||||
return totalSompiInAfter, nil
|
return totalSompiInAfter, nil
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ValidateTransactionInIsolation validates the parts of the transaction that can be validated context-free
|
// ValidateTransactionInIsolation validates the parts of the transaction that can be validated context-free
|
||||||
func (v *transactionValidator) ValidateTransactionInIsolation(tx *externalapi.DomainTransaction) error {
|
func (v *transactionValidator) ValidateTransactionInIsolation(tx *externalapi.DomainTransaction, povDAAScore uint64) error {
|
||||||
|
isHF1Activated := povDAAScore >= v.hf1DAAScore
|
||||||
err := v.checkTransactionInputCount(tx)
|
err := v.checkTransactionInputCount(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = v.checkTransactionAmountRanges(tx)
|
err = v.checkTransactionAmountRanges(tx, isHF1Activated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -62,13 +63,18 @@ func (v *transactionValidator) checkTransactionInputCount(tx *externalapi.Domain
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *transactionValidator) checkTransactionAmountRanges(tx *externalapi.DomainTransaction) error {
|
func (v *transactionValidator) checkTransactionAmountRanges(tx *externalapi.DomainTransaction, isHF1Activated bool) error {
|
||||||
// Ensure the transaction amounts are in range. Each transaction
|
// Ensure the transaction amounts are in range. Each transaction
|
||||||
// output must not be negative or more than the max allowed per
|
// output must not be negative or more than the max allowed per
|
||||||
// transaction. Also, the total of all outputs must abide by the same
|
// transaction. Also, the total of all outputs must abide by the same
|
||||||
// restrictions. All amounts in a transaction are in a unit value known
|
// restrictions. All amounts in a transaction are in a unit value known
|
||||||
// as a sompi. One kaspa is a quantity of sompi as defined by the
|
// as a sompi. One kaspa is a quantity of sompi as defined by the
|
||||||
// sompiPerKaspa constant.
|
// sompiPerKaspa constant.
|
||||||
|
maxSompi := constants.MaxSompiBeforeHF1
|
||||||
|
if isHF1Activated {
|
||||||
|
maxSompi = constants.MaxSompiAfterHF1
|
||||||
|
}
|
||||||
|
|
||||||
var totalSompi uint64
|
var totalSompi uint64
|
||||||
for _, txOut := range tx.Outputs {
|
for _, txOut := range tx.Outputs {
|
||||||
sompi := txOut.Value
|
sompi := txOut.Value
|
||||||
@ -76,9 +82,9 @@ func (v *transactionValidator) checkTransactionAmountRanges(tx *externalapi.Doma
|
|||||||
return errors.Wrap(ruleerrors.ErrTxOutValueZero, "zero value outputs are forbidden")
|
return errors.Wrap(ruleerrors.ErrTxOutValueZero, "zero value outputs are forbidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sompi > constants.MaxSompi {
|
if sompi > maxSompi {
|
||||||
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "transaction output value of %d is "+
|
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "transaction output value of %d is "+
|
||||||
"higher than max allowed value of %d", sompi, constants.MaxSompi)
|
"higher than max allowed value of %d", sompi, maxSompi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binary arithmetic guarantees that any overflow is detected and reported.
|
// Binary arithmetic guarantees that any overflow is detected and reported.
|
||||||
@ -88,14 +94,14 @@ func (v *transactionValidator) checkTransactionAmountRanges(tx *externalapi.Doma
|
|||||||
if newTotalSompi < totalSompi {
|
if newTotalSompi < totalSompi {
|
||||||
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
|
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
|
||||||
"outputs exceeds max allowed value of %d",
|
"outputs exceeds max allowed value of %d",
|
||||||
constants.MaxSompi)
|
maxSompi)
|
||||||
}
|
}
|
||||||
totalSompi = newTotalSompi
|
totalSompi = newTotalSompi
|
||||||
if totalSompi > constants.MaxSompi {
|
if totalSompi > maxSompi {
|
||||||
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
|
return errors.Wrapf(ruleerrors.ErrBadTxOutValue, "total value of all transaction "+
|
||||||
"outputs is %d which is higher than max "+
|
"outputs is %d which is higher than max "+
|
||||||
"allowed value of %d", totalSompi,
|
"allowed value of %d", totalSompi,
|
||||||
constants.MaxSompi)
|
maxSompi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,11 @@ type txSubnetworkData struct {
|
|||||||
|
|
||||||
func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
|
cfg := *consensusConfig
|
||||||
|
cfg.HF1DAAScore = 20
|
||||||
|
|
||||||
factory := consensus.NewFactory()
|
factory := consensus.NewFactory()
|
||||||
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestValidateTransactionInIsolationAndPopulateMass")
|
tc, teardown, err := factory.NewTestConsensus(&cfg, "TestValidateTransactionInIsolationAndPopulateMass")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error setting up consensus: %+v", err)
|
t.Fatalf("Error setting up consensus: %+v", err)
|
||||||
}
|
}
|
||||||
@ -37,25 +40,36 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
|||||||
txSubnetworkData *txSubnetworkData
|
txSubnetworkData *txSubnetworkData
|
||||||
extraModificationsFunc func(*externalapi.DomainTransaction)
|
extraModificationsFunc func(*externalapi.DomainTransaction)
|
||||||
expectedErr error
|
expectedErr error
|
||||||
|
daaScore uint64
|
||||||
}{
|
}{
|
||||||
{"good one", 1, 1, 1, subnetworks.SubnetworkIDNative, nil, nil, nil},
|
{"good one", 1, 1, 1, subnetworks.SubnetworkIDNative, nil, nil, nil, 0},
|
||||||
{"no inputs", 0, 1, 1, subnetworks.SubnetworkIDNative, nil, nil, ruleerrors.ErrNoTxInputs},
|
{"no inputs", 0, 1, 1, subnetworks.SubnetworkIDNative, nil, nil, ruleerrors.ErrNoTxInputs, 0},
|
||||||
{"no outputs", 1, 0, 1, subnetworks.SubnetworkIDNative, nil, nil, nil},
|
{"no outputs", 1, 0, 1, subnetworks.SubnetworkIDNative, nil, nil, nil, 0},
|
||||||
{"too much sompi in one output", 1, 1, constants.MaxSompi + 1,
|
{"too much sompi in one output", 1, 1, constants.MaxSompiBeforeHF1 + 1,
|
||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
ruleerrors.ErrBadTxOutValue},
|
ruleerrors.ErrBadTxOutValue, 0},
|
||||||
{"too much sompi in total outputs", 1, 2, constants.MaxSompi - 1,
|
{"too much sompi before- valid now", 1, 1, constants.MaxSompiBeforeHF1 + 1,
|
||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
ruleerrors.ErrBadTxOutValue},
|
nil, cfg.HF1DAAScore},
|
||||||
|
{"too much sompi in one output - after hf", 1, 1, constants.MaxSompiAfterHF1 + 1,
|
||||||
|
subnetworks.SubnetworkIDNative,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
ruleerrors.ErrBadTxOutValue, cfg.HF1DAAScore},
|
||||||
|
{"too much sompi in one output", 1, 1, constants.MaxSompiAfterHF1 + 1,
|
||||||
|
subnetworks.SubnetworkIDNative,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
ruleerrors.ErrBadTxOutValue, 0},
|
||||||
{"duplicate inputs", 2, 1, 1,
|
{"duplicate inputs", 2, 1, 1,
|
||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
nil,
|
nil,
|
||||||
func(tx *externalapi.DomainTransaction) { tx.Inputs[1].PreviousOutpoint.Index = 0 },
|
func(tx *externalapi.DomainTransaction) { tx.Inputs[1].PreviousOutpoint.Index = 0 },
|
||||||
ruleerrors.ErrDuplicateTxInputs},
|
ruleerrors.ErrDuplicateTxInputs, 0},
|
||||||
{"1 input coinbase",
|
{"1 input coinbase",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
@ -63,7 +77,7 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
|||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil},
|
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil},
|
||||||
nil,
|
nil,
|
||||||
nil},
|
nil, 0},
|
||||||
{"no inputs coinbase",
|
{"no inputs coinbase",
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@ -71,7 +85,7 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
|||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil},
|
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, nil},
|
||||||
nil,
|
nil,
|
||||||
nil},
|
nil, 0},
|
||||||
{"too long payload coinbase",
|
{"too long payload coinbase",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
@ -79,26 +93,26 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
|||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, make([]byte, consensusConfig.MaxCoinbasePayloadLength+1)},
|
&txSubnetworkData{subnetworks.SubnetworkIDCoinbase, 0, make([]byte, consensusConfig.MaxCoinbasePayloadLength+1)},
|
||||||
nil,
|
nil,
|
||||||
ruleerrors.ErrBadCoinbasePayloadLen},
|
ruleerrors.ErrBadCoinbasePayloadLen, 0},
|
||||||
{"non-zero gas in Kaspa", 1, 1, 1,
|
{"non-zero gas in Kaspa", 1, 1, 1,
|
||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
nil,
|
nil,
|
||||||
func(tx *externalapi.DomainTransaction) {
|
func(tx *externalapi.DomainTransaction) {
|
||||||
tx.Gas = 1
|
tx.Gas = 1
|
||||||
},
|
},
|
||||||
ruleerrors.ErrInvalidGas},
|
ruleerrors.ErrInvalidGas, 0},
|
||||||
{"non-zero gas in subnetwork registry", 1, 1, 1,
|
{"non-zero gas in subnetwork registry", 1, 1, 1,
|
||||||
subnetworks.SubnetworkIDRegistry,
|
subnetworks.SubnetworkIDRegistry,
|
||||||
&txSubnetworkData{subnetworks.SubnetworkIDRegistry, 1, []byte{}},
|
&txSubnetworkData{subnetworks.SubnetworkIDRegistry, 1, []byte{}},
|
||||||
nil,
|
nil,
|
||||||
ruleerrors.ErrInvalidGas},
|
ruleerrors.ErrInvalidGas, 0},
|
||||||
{"non-zero payload in Kaspa", 1, 1, 1,
|
{"non-zero payload in Kaspa", 1, 1, 1,
|
||||||
subnetworks.SubnetworkIDNative,
|
subnetworks.SubnetworkIDNative,
|
||||||
nil,
|
nil,
|
||||||
func(tx *externalapi.DomainTransaction) {
|
func(tx *externalapi.DomainTransaction) {
|
||||||
tx.Payload = []byte{1}
|
tx.Payload = []byte{1}
|
||||||
},
|
},
|
||||||
ruleerrors.ErrInvalidPayload},
|
ruleerrors.ErrInvalidPayload, 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
@ -108,7 +122,7 @@ func TestValidateTransactionInIsolationAndPopulateMass(t *testing.T) {
|
|||||||
test.extraModificationsFunc(tx)
|
test.extraModificationsFunc(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tc.TransactionValidator().ValidateTransactionInIsolation(tx)
|
err := tc.TransactionValidator().ValidateTransactionInIsolation(tx, test.daaScore)
|
||||||
if !errors.Is(err, test.expectedErr) {
|
if !errors.Is(err, test.expectedErr) {
|
||||||
t.Errorf("TestValidateTransactionInIsolationAndPopulateMass: '%s': unexpected error %+v", test.name, err)
|
t.Errorf("TestValidateTransactionInIsolationAndPopulateMass: '%s': unexpected error %+v", test.name, err)
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ type transactionValidator struct {
|
|||||||
sigCache *txscript.SigCache
|
sigCache *txscript.SigCache
|
||||||
sigCacheECDSA *txscript.SigCacheECDSA
|
sigCacheECDSA *txscript.SigCacheECDSA
|
||||||
txMassCalculator *txmass.Calculator
|
txMassCalculator *txmass.Calculator
|
||||||
|
hf1DAAScore uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instantiates a new TransactionValidator
|
// New instantiates a new TransactionValidator
|
||||||
@ -31,7 +32,8 @@ func New(blockCoinbaseMaturity uint64,
|
|||||||
pastMedianTimeManager model.PastMedianTimeManager,
|
pastMedianTimeManager model.PastMedianTimeManager,
|
||||||
ghostdagDataStore model.GHOSTDAGDataStore,
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
||||||
daaBlocksStore model.DAABlocksStore,
|
daaBlocksStore model.DAABlocksStore,
|
||||||
txMassCalculator *txmass.Calculator) model.TransactionValidator {
|
txMassCalculator *txmass.Calculator,
|
||||||
|
hf1DAAScore uint64) model.TransactionValidator {
|
||||||
|
|
||||||
return &transactionValidator{
|
return &transactionValidator{
|
||||||
blockCoinbaseMaturity: blockCoinbaseMaturity,
|
blockCoinbaseMaturity: blockCoinbaseMaturity,
|
||||||
@ -44,5 +46,6 @@ func New(blockCoinbaseMaturity uint64,
|
|||||||
sigCache: txscript.NewSigCache(sigCacheSize),
|
sigCache: txscript.NewSigCache(sigCacheSize),
|
||||||
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
||||||
txMassCalculator: txMassCalculator,
|
txMassCalculator: txMassCalculator,
|
||||||
|
hf1DAAScore: hf1DAAScore,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
|
consensusConfig.HF1DAAScore = consensusConfig.GenesisBlock.Header.DAAScore() + 1000
|
||||||
factory := consensus.NewFactory()
|
factory := consensus.NewFactory()
|
||||||
tc, tearDown, err := factory.NewTestConsensus(consensusConfig,
|
tc, tearDown, err := factory.NewTestConsensus(consensusConfig,
|
||||||
"TestValidateTransactionInContextAndPopulateFee")
|
"TestValidateTransactionInContextAndPopulateFee")
|
||||||
@ -85,17 +85,31 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
|||||||
true,
|
true,
|
||||||
uint64(6)),
|
uint64(6)),
|
||||||
}
|
}
|
||||||
|
|
||||||
txInputWithLargeAmount := externalapi.DomainTransactionInput{
|
txInputWithLargeAmount := externalapi.DomainTransactionInput{
|
||||||
PreviousOutpoint: prevOutPoint,
|
PreviousOutpoint: prevOutPoint,
|
||||||
SignatureScript: []byte{},
|
SignatureScript: []byte{},
|
||||||
Sequence: constants.MaxTxInSequenceNum,
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
SigOpCount: 1,
|
SigOpCount: 1,
|
||||||
UTXOEntry: utxo.NewUTXOEntry(
|
UTXOEntry: utxo.NewUTXOEntry(
|
||||||
constants.MaxSompi,
|
constants.MaxSompiBeforeHF1+1,
|
||||||
scriptPublicKey,
|
scriptPublicKey,
|
||||||
true,
|
false,
|
||||||
uint64(5)),
|
0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
txInputWithLargeAmountAfterHF := externalapi.DomainTransactionInput{
|
||||||
|
PreviousOutpoint: prevOutPoint,
|
||||||
|
SignatureScript: []byte{},
|
||||||
|
Sequence: constants.MaxTxInSequenceNum,
|
||||||
|
SigOpCount: 1,
|
||||||
|
UTXOEntry: utxo.NewUTXOEntry(
|
||||||
|
constants.MaxSompiAfterHF1+1,
|
||||||
|
scriptPublicKey,
|
||||||
|
false,
|
||||||
|
0),
|
||||||
|
}
|
||||||
|
|
||||||
txInputWithBadSigOpCount := externalapi.DomainTransactionInput{
|
txInputWithBadSigOpCount := externalapi.DomainTransactionInput{
|
||||||
PreviousOutpoint: prevOutPoint,
|
PreviousOutpoint: prevOutPoint,
|
||||||
SignatureScript: []byte{},
|
SignatureScript: []byte{},
|
||||||
@ -141,13 +155,31 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
|||||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||||
Gas: 0,
|
Gas: 0,
|
||||||
LockTime: 0}
|
LockTime: 0}
|
||||||
txWithLargeAmount := externalapi.DomainTransaction{
|
txWithLargeAmountBeforeHF := externalapi.DomainTransaction{
|
||||||
Version: constants.MaxTransactionVersion,
|
Version: constants.MaxTransactionVersion,
|
||||||
Inputs: []*externalapi.DomainTransactionInput{&txInput, &txInputWithLargeAmount},
|
Inputs: []*externalapi.DomainTransactionInput{&txInputWithLargeAmount},
|
||||||
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||||
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||||
Gas: 0,
|
Gas: 0,
|
||||||
LockTime: 0}
|
LockTime: 0}
|
||||||
|
|
||||||
|
txWithLargeAmountAfterHF := externalapi.DomainTransaction{
|
||||||
|
Version: constants.MaxTransactionVersion,
|
||||||
|
Inputs: []*externalapi.DomainTransactionInput{&txInputWithLargeAmountAfterHF},
|
||||||
|
Outputs: []*externalapi.DomainTransactionOutput{&txOutput},
|
||||||
|
SubnetworkID: subnetworks.SubnetworkIDRegistry,
|
||||||
|
Gas: 0,
|
||||||
|
LockTime: 0}
|
||||||
|
|
||||||
|
for i, input := range txWithLargeAmountBeforeHF.Inputs {
|
||||||
|
signatureScript, err := txscript.SignatureScript(&txWithLargeAmountBeforeHF, i, consensushashing.SigHashAll, privateKey,
|
||||||
|
&consensushashing.SighashReusedValues{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create a sigScript: %v", err)
|
||||||
|
}
|
||||||
|
input.SignatureScript = signatureScript
|
||||||
|
}
|
||||||
|
|
||||||
txWithBigValue := externalapi.DomainTransaction{
|
txWithBigValue := externalapi.DomainTransaction{
|
||||||
Version: constants.MaxTransactionVersion,
|
Version: constants.MaxTransactionVersion,
|
||||||
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
Inputs: []*externalapi.DomainTransactionInput{&txInput},
|
||||||
@ -175,6 +207,9 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
|||||||
povBlockHash := externalapi.NewDomainHashFromByteArray(&[32]byte{0x01})
|
povBlockHash := externalapi.NewDomainHashFromByteArray(&[32]byte{0x01})
|
||||||
tc.DAABlocksStore().StageDAAScore(stagingArea, povBlockHash, consensusConfig.BlockCoinbaseMaturity+txInput.UTXOEntry.BlockDAAScore())
|
tc.DAABlocksStore().StageDAAScore(stagingArea, povBlockHash, consensusConfig.BlockCoinbaseMaturity+txInput.UTXOEntry.BlockDAAScore())
|
||||||
|
|
||||||
|
povAfterHFBlockHash := externalapi.NewDomainHashFromByteArray(&[32]byte{0x02})
|
||||||
|
tc.DAABlocksStore().StageDAAScore(stagingArea, povAfterHFBlockHash, consensusConfig.HF1DAAScore)
|
||||||
|
|
||||||
// Just use some stub ghostdag data
|
// Just use some stub ghostdag data
|
||||||
tc.GHOSTDAGDataStore().Stage(stagingArea, povBlockHash, externalapi.NewBlockGHOSTDAGData(
|
tc.GHOSTDAGDataStore().Stage(stagingArea, povBlockHash, externalapi.NewBlockGHOSTDAGData(
|
||||||
0,
|
0,
|
||||||
@ -207,13 +242,27 @@ func TestValidateTransactionInContextAndPopulateFee(t *testing.T) {
|
|||||||
isValid: false,
|
isValid: false,
|
||||||
expectedError: ruleerrors.ErrImmatureSpend,
|
expectedError: ruleerrors.ErrImmatureSpend,
|
||||||
},
|
},
|
||||||
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompi)
|
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompiBeforeHF1)
|
||||||
name: "checkTransactionInputAmounts",
|
name: "checkTransactionInputAmounts - invalid - before HF",
|
||||||
tx: &txWithLargeAmount,
|
tx: &txWithLargeAmountBeforeHF,
|
||||||
povBlockHash: povBlockHash,
|
povBlockHash: povBlockHash,
|
||||||
isValid: false,
|
isValid: false,
|
||||||
expectedError: ruleerrors.ErrBadTxOutValue,
|
expectedError: ruleerrors.ErrBadTxOutValue,
|
||||||
},
|
},
|
||||||
|
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompiBeforeHF1)
|
||||||
|
name: "checkTransactionInputAmounts - valid - after HF",
|
||||||
|
tx: &txWithLargeAmountBeforeHF,
|
||||||
|
povBlockHash: povAfterHFBlockHash,
|
||||||
|
isValid: true,
|
||||||
|
expectedError: nil,
|
||||||
|
},
|
||||||
|
{ // The total inputs amount is bigger than the allowed maximum (constants.MaxSompiBeforeHF1)
|
||||||
|
name: "checkTransactionInputAmounts - invalid - after HF",
|
||||||
|
tx: &txWithLargeAmountAfterHF,
|
||||||
|
povBlockHash: povAfterHFBlockHash,
|
||||||
|
isValid: false,
|
||||||
|
expectedError: ruleerrors.ErrBadTxOutValue,
|
||||||
|
},
|
||||||
{ // The total SompiIn (sum of inputs amount) is smaller than the total SompiOut (sum of outputs value) and hence invalid.
|
{ // The total SompiIn (sum of inputs amount) is smaller than the total SompiOut (sum of outputs value) and hence invalid.
|
||||||
name: "checkTransactionOutputAmounts",
|
name: "checkTransactionOutputAmounts",
|
||||||
tx: &txWithBigValue,
|
tx: &txWithBigValue,
|
||||||
|
@ -240,6 +240,7 @@ var (
|
|||||||
ErrPruningProofMissesBlocksBelowPruningPoint = newRuleError("ErrPruningProofMissesBlocksBelowPruningPoint")
|
ErrPruningProofMissesBlocksBelowPruningPoint = newRuleError("ErrPruningProofMissesBlocksBelowPruningPoint")
|
||||||
ErrPruningProofEmpty = newRuleError("ErrPruningProofEmpty")
|
ErrPruningProofEmpty = newRuleError("ErrPruningProofEmpty")
|
||||||
ErrWrongCoinbaseSubsidy = newRuleError("ErrWrongCoinbaseSubsidy")
|
ErrWrongCoinbaseSubsidy = newRuleError("ErrWrongCoinbaseSubsidy")
|
||||||
|
ErrWrongBlockVersion = newRuleError("ErrWrongBlockVersion")
|
||||||
)
|
)
|
||||||
|
|
||||||
// RuleError identifies a rule violation. It is used to indicate that
|
// RuleError identifies a rule violation. It is used to indicate that
|
||||||
|
@ -3,9 +3,11 @@ package constants
|
|||||||
import "math"
|
import "math"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// MaxBlockVersion represents the current version of blocks mined and the maximum block version
|
// BlockVersionBeforeHF1 represents the current version of blocks mined before HF1
|
||||||
// this node is able to validate
|
BlockVersionBeforeHF1 uint16 = 0
|
||||||
MaxBlockVersion uint16 = 0
|
|
||||||
|
// BlockVersionAfterHF1 represents the current version of blocks mined after HF1
|
||||||
|
BlockVersionAfterHF1 uint16 = 1
|
||||||
|
|
||||||
// MaxTransactionVersion is the current latest supported transaction version.
|
// MaxTransactionVersion is the current latest supported transaction version.
|
||||||
MaxTransactionVersion uint16 = 0
|
MaxTransactionVersion uint16 = 0
|
||||||
@ -16,8 +18,11 @@ const (
|
|||||||
// SompiPerKaspa is the number of sompi in one kaspa (1 KAS).
|
// SompiPerKaspa is the number of sompi in one kaspa (1 KAS).
|
||||||
SompiPerKaspa = 100_000_000
|
SompiPerKaspa = 100_000_000
|
||||||
|
|
||||||
// MaxSompi is the maximum transaction amount allowed in sompi.
|
// MaxSompiBeforeHF1 is the maximum transaction amount allowed in sompi before the HF1 hard fork is activated.
|
||||||
MaxSompi = 21_000_000 * SompiPerKaspa
|
MaxSompiBeforeHF1 = uint64(21_000_000 * SompiPerKaspa)
|
||||||
|
|
||||||
|
// MaxSompiAfterHF1 is the maximum transaction amount allowed in sompi after the HF1 hard fork is activated.
|
||||||
|
MaxSompiAfterHF1 = uint64(29_000_000_000 * SompiPerKaspa)
|
||||||
|
|
||||||
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||||
// of a transaction input can be.
|
// of a transaction input can be.
|
||||||
|
@ -82,4 +82,6 @@ const (
|
|||||||
// The network was down for three days shortly after launch
|
// The network was down for three days shortly after launch
|
||||||
// Three days in seconds = 3 * 24 * 60 * 60 = 259200
|
// Three days in seconds = 3 * 24 * 60 * 60 = 259200
|
||||||
defaultDeflationaryPhaseDaaScore = 15778800 - 259200
|
defaultDeflationaryPhaseDaaScore = 15778800 - 259200
|
||||||
|
|
||||||
|
defaultMergeDepth = 3600
|
||||||
)
|
)
|
||||||
|
@ -188,6 +188,9 @@ type Params struct {
|
|||||||
|
|
||||||
// MaxBlockLevel is the maximum possible block level.
|
// MaxBlockLevel is the maximum possible block level.
|
||||||
MaxBlockLevel int
|
MaxBlockLevel int
|
||||||
|
|
||||||
|
MergeDepth uint64
|
||||||
|
HF1DAAScore uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeRPCServerAddress returns addr with the current network default
|
// NormalizeRPCServerAddress returns addr with the current network default
|
||||||
@ -286,12 +289,14 @@ var MainnetParams = Params{
|
|||||||
// This is technically 255, but we clamped it at 256 - block level of mainnet genesis
|
// This is technically 255, but we clamped it at 256 - block level of mainnet genesis
|
||||||
// This means that any block that has a level lower or equal to genesis will be level 0.
|
// This means that any block that has a level lower or equal to genesis will be level 0.
|
||||||
MaxBlockLevel: 225,
|
MaxBlockLevel: 225,
|
||||||
|
MergeDepth: defaultMergeDepth,
|
||||||
|
HF1DAAScore: 14360917,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestnetParams defines the network parameters for the test Kaspa network.
|
// TestnetParams defines the network parameters for the test Kaspa network.
|
||||||
var TestnetParams = Params{
|
var TestnetParams = Params{
|
||||||
K: defaultGHOSTDAGK,
|
K: defaultGHOSTDAGK,
|
||||||
Name: "kaspa-testnet-9",
|
Name: "kaspa-testnet-10",
|
||||||
Net: appmessage.Testnet,
|
Net: appmessage.Testnet,
|
||||||
RPCPort: "16210",
|
RPCPort: "16210",
|
||||||
DefaultPort: "16211",
|
DefaultPort: "16211",
|
||||||
@ -348,6 +353,8 @@ var TestnetParams = Params{
|
|||||||
IgnoreHeaderMass: true,
|
IgnoreHeaderMass: true,
|
||||||
|
|
||||||
MaxBlockLevel: 250,
|
MaxBlockLevel: 250,
|
||||||
|
MergeDepth: defaultMergeDepth,
|
||||||
|
HF1DAAScore: 172800,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimnetParams defines the network parameters for the simulation test Kaspa
|
// SimnetParams defines the network parameters for the simulation test Kaspa
|
||||||
@ -413,6 +420,7 @@ var SimnetParams = Params{
|
|||||||
DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
|
DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
|
||||||
|
|
||||||
MaxBlockLevel: 250,
|
MaxBlockLevel: 250,
|
||||||
|
MergeDepth: defaultMergeDepth,
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevnetParams defines the network parameters for the development Kaspa network.
|
// DevnetParams defines the network parameters for the development Kaspa network.
|
||||||
@ -475,6 +483,7 @@ var DevnetParams = Params{
|
|||||||
IgnoreHeaderMass: true,
|
IgnoreHeaderMass: true,
|
||||||
|
|
||||||
MaxBlockLevel: 250,
|
MaxBlockLevel: 250,
|
||||||
|
MergeDepth: defaultMergeDepth,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrDuplicateNet describes an error where the parameters for a Kaspa
|
// ErrDuplicateNet describes an error where the parameters for a Kaspa
|
||||||
|
@ -198,8 +198,9 @@ func (mp *mempool) minimumRequiredTransactionRelayFee(mass uint64) uint64 {
|
|||||||
|
|
||||||
// Set the minimum fee to the maximum possible value if the calculated
|
// Set the minimum fee to the maximum possible value if the calculated
|
||||||
// fee is not in the valid range for monetary amounts.
|
// fee is not in the valid range for monetary amounts.
|
||||||
if minimumFee > constants.MaxSompi {
|
// TODO: Replace it with constants.MaxSompiAfterHF1 once HF is activated
|
||||||
minimumFee = constants.MaxSompi
|
if minimumFee > constants.MaxSompiBeforeHF1 {
|
||||||
|
minimumFee = constants.MaxSompiBeforeHF1
|
||||||
}
|
}
|
||||||
|
|
||||||
return minimumFee
|
return minimumFee
|
||||||
|
@ -53,8 +53,8 @@ func TestCalcMinRequiredTxRelayFee(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"max standard tx size with max sompi relay fee",
|
"max standard tx size with max sompi relay fee",
|
||||||
MaximumStandardTransactionMass,
|
MaximumStandardTransactionMass,
|
||||||
constants.MaxSompi,
|
util.Amount(constants.MaxSompiBeforeHF1),
|
||||||
constants.MaxSompi,
|
constants.MaxSompiBeforeHF1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"1500 bytes with 5000 relay fee",
|
"1500 bytes with 5000 relay fee",
|
||||||
@ -156,8 +156,8 @@ func TestIsTransactionOutputDust(t *testing.T) {
|
|||||||
{
|
{
|
||||||
// Maximum allowed value is never dust.
|
// Maximum allowed value is never dust.
|
||||||
"max sompi amount is never dust",
|
"max sompi amount is never dust",
|
||||||
externalapi.DomainTransactionOutput{Value: constants.MaxSompi, ScriptPublicKey: scriptPublicKey},
|
externalapi.DomainTransactionOutput{Value: constants.MaxSompiBeforeHF1, ScriptPublicKey: scriptPublicKey},
|
||||||
constants.MaxSompi,
|
util.Amount(constants.MaxSompiBeforeHF1),
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -31,50 +31,3 @@ func TestAddressExchange(t *testing.T) {
|
|||||||
|
|
||||||
t.Errorf("Didn't find testAddress in list of addresses of appHarness3")
|
t.Errorf("Didn't find testAddress in list of addresses of appHarness3")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddressExchangeV3V4(t *testing.T) {
|
|
||||||
harnesses, teardown := setupHarnesses(t, []*harnessParams{
|
|
||||||
{
|
|
||||||
p2pAddress: p2pAddress1,
|
|
||||||
rpcAddress: rpcAddress1,
|
|
||||||
miningAddress: miningAddress1,
|
|
||||||
miningAddressPrivateKey: miningAddress1PrivateKey,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
p2pAddress: p2pAddress2,
|
|
||||||
rpcAddress: rpcAddress2,
|
|
||||||
miningAddress: miningAddress2,
|
|
||||||
miningAddressPrivateKey: miningAddress2PrivateKey,
|
|
||||||
}, {
|
|
||||||
p2pAddress: p2pAddress3,
|
|
||||||
rpcAddress: rpcAddress3,
|
|
||||||
miningAddress: miningAddress3,
|
|
||||||
miningAddressPrivateKey: miningAddress3PrivateKey,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
appHarness1, appHarness2, appHarness3 := harnesses[0], harnesses[1], harnesses[2]
|
|
||||||
|
|
||||||
testAddress := "1.2.3.4:6789"
|
|
||||||
err := addressmanager.AddAddressByIP(appHarness1.app.AddressManager(), testAddress, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error adding address to addressManager: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(t, appHarness1, appHarness2)
|
|
||||||
connect(t, appHarness2, appHarness3)
|
|
||||||
|
|
||||||
peerAddresses, err := appHarness3.rpcClient.GetPeerAddresses()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error getting peer addresses: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, peerAddress := range peerAddresses.Addresses {
|
|
||||||
if peerAddress.Addr == testAddress {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Errorf("Didn't find testAddress in list of addresses of appHarness3")
|
|
||||||
}
|
|
||||||
|
@ -238,3 +238,84 @@ func mineNextBlockWithMockTimestamps(t *testing.T, harness *appHarness, rd *rand
|
|||||||
|
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBoundedMergeDepth(t *testing.T) {
|
||||||
|
overrideDAGParams := dagconfig.SimnetParams
|
||||||
|
|
||||||
|
overrideDAGParams.MergeDepth = 50
|
||||||
|
|
||||||
|
harnesses, teardown := setupHarnesses(t, []*harnessParams{
|
||||||
|
{
|
||||||
|
p2pAddress: p2pAddress1,
|
||||||
|
rpcAddress: rpcAddress1,
|
||||||
|
miningAddress: miningAddress1,
|
||||||
|
miningAddressPrivateKey: miningAddress1PrivateKey,
|
||||||
|
overrideDAGParams: &overrideDAGParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
p2pAddress: p2pAddress2,
|
||||||
|
rpcAddress: rpcAddress2,
|
||||||
|
miningAddress: miningAddress2,
|
||||||
|
miningAddressPrivateKey: miningAddress2PrivateKey,
|
||||||
|
overrideDAGParams: &overrideDAGParams,
|
||||||
|
utxoIndex: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
p2pAddress: p2pAddress3,
|
||||||
|
rpcAddress: rpcAddress3,
|
||||||
|
miningAddress: miningAddress3,
|
||||||
|
miningAddressPrivateKey: miningAddress3PrivateKey,
|
||||||
|
overrideDAGParams: &overrideDAGParams,
|
||||||
|
utxoIndex: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
p2pAddress: p2pAddress4,
|
||||||
|
rpcAddress: rpcAddress4,
|
||||||
|
miningAddress: miningAddress3,
|
||||||
|
miningAddressPrivateKey: miningAddress3PrivateKey,
|
||||||
|
overrideDAGParams: &overrideDAGParams,
|
||||||
|
utxoIndex: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
test := func(syncer, syncee *appHarness, depth uint64, shouldSync bool) {
|
||||||
|
const ibdTriggerRange = 32
|
||||||
|
if depth <= ibdTriggerRange {
|
||||||
|
t.Fatalf("Depth is too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < depth+ibdTriggerRange+1; i++ {
|
||||||
|
mineNextBlock(t, syncee)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := uint64(0); i < ibdTriggerRange+1; i++ {
|
||||||
|
mineNextBlock(t, syncer)
|
||||||
|
}
|
||||||
|
|
||||||
|
countBefore, err := syncee.rpcClient.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlockCount: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(t, syncee, syncer)
|
||||||
|
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
countAfter, err := syncee.rpcClient.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetBlockCount: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (countBefore.HeaderCount != countAfter.HeaderCount) != shouldSync {
|
||||||
|
t.Fatalf("countBefore.HeaderCount: %d, countAfter.HeaderCount: %d", countBefore.HeaderCount, countAfter.HeaderCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("mergeDepth", func(t *testing.T) {
|
||||||
|
test(harnesses[0], harnesses[1], overrideDAGParams.MergeDepth, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mergeDepth-1", func(t *testing.T) {
|
||||||
|
test(harnesses[2], harnesses[3], overrideDAGParams.MergeDepth-1, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -73,6 +73,8 @@ func round(f float64) Amount {
|
|||||||
// NewAmount is for specifically for converting KAS to Sompi.
|
// NewAmount is for specifically for converting KAS to Sompi.
|
||||||
// For creating a new Amount with an int64 value which denotes a quantity of Sompi,
|
// For creating a new Amount with an int64 value which denotes a quantity of Sompi,
|
||||||
// do a simple type conversion from type int64 to Amount.
|
// do a simple type conversion from type int64 to Amount.
|
||||||
|
// TODO: Refactor NewAmount. When amounts are more than 1e9 KAS, the precision
|
||||||
|
// can be higher than one sompi (1e9 and 1e9+1e-8 will result as the same number)
|
||||||
func NewAmount(f float64) (Amount, error) {
|
func NewAmount(f float64) (Amount, error) {
|
||||||
// The amount is only considered invalid if it cannot be represented
|
// The amount is only considered invalid if it cannot be represented
|
||||||
// as an integer type. This may happen if f is NaN or +-Infinity.
|
// as an integer type. This may happen if f is NaN or +-Infinity.
|
||||||
|
@ -30,13 +30,13 @@ func TestAmountCreation(t *testing.T) {
|
|||||||
name: "max producible",
|
name: "max producible",
|
||||||
amount: 21e6,
|
amount: 21e6,
|
||||||
valid: true,
|
valid: true,
|
||||||
expected: constants.MaxSompi,
|
expected: Amount(constants.MaxSompiBeforeHF1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "exceeds max producible",
|
name: "exceeds max producible",
|
||||||
amount: 21e6 + 1e-8,
|
amount: 21e6 + 1e-8,
|
||||||
valid: true,
|
valid: true,
|
||||||
expected: constants.MaxSompi + 1,
|
expected: Amount(constants.MaxSompiBeforeHF1) + 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "one hundred",
|
name: "one hundred",
|
||||||
@ -109,7 +109,7 @@ func TestAmountUnitConversions(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "MKAS",
|
name: "MKAS",
|
||||||
amount: constants.MaxSompi,
|
amount: Amount(constants.MaxSompiBeforeHF1),
|
||||||
unit: AmountMegaKAS,
|
unit: AmountMegaKAS,
|
||||||
converted: 21,
|
converted: 21,
|
||||||
s: "21 MKAS",
|
s: "21 MKAS",
|
||||||
|
@ -10,8 +10,8 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
appMajor uint = 0
|
appMajor uint = 0
|
||||||
appMinor uint = 11
|
appMinor uint = 12
|
||||||
appPatch uint = 17
|
appPatch uint = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
// appBuild is defined as a variable so it can be overridden during the build
|
// appBuild is defined as a variable so it can be overridden during the build
|
||||||
|
Loading…
x
Reference in New Issue
Block a user