ubqhash/consensus: refactor for uint64 timestamps

This commit is contained in:
Luke Williams 2019-05-16 17:43:15 +02:00
parent 16fc5d0b52
commit cdb4f3ef5b

View file

@ -26,19 +26,21 @@ import (
mapset "github.com/deckarep/golang-set" mapset "github.com/deckarep/golang-set"
"github.com/ubiq/go-ubiq/common" "github.com/ubiq/go-ubiq/common"
"github.com/ubiq/go-ubiq/common/math"
"github.com/ubiq/go-ubiq/consensus" "github.com/ubiq/go-ubiq/consensus"
"github.com/ubiq/go-ubiq/consensus/misc" "github.com/ubiq/go-ubiq/consensus/misc"
"github.com/ubiq/go-ubiq/core/state" "github.com/ubiq/go-ubiq/core/state"
"github.com/ubiq/go-ubiq/core/types" "github.com/ubiq/go-ubiq/core/types"
"github.com/ubiq/go-ubiq/log" "github.com/ubiq/go-ubiq/log"
"github.com/ubiq/go-ubiq/params" "github.com/ubiq/go-ubiq/params"
"github.com/ubiq/go-ubiq/rlp"
"golang.org/x/crypto/sha3"
) )
// Ubqhash proof-of-work protocol constants. // Ubqhash proof-of-work protocol constants.
var ( var (
blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block
maxUncles = 2 // Maximum number of uncles allowed in a single block maxUncles = 2 // Maximum number of uncles allowed in a single block
allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks
) )
// Diff algo constants. // Diff algo constants.
@ -246,42 +248,39 @@ func (ubqhash *Ubqhash) verifyHeader(chain consensus.ChainReader, header, parent
return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize) return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
} }
// Verify the header's timestamp // Verify the header's timestamp
if uncle { if !uncle {
if header.Time.Cmp(math.MaxBig256) > 0 { if header.Time > uint64(time.Now().Add(allowedFutureBlockTime).Unix()) {
return errLargeBlockTime
}
} else {
if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 {
return consensus.ErrFutureBlock return consensus.ErrFutureBlock
} }
} }
if header.Time.Cmp(parent.Time) <= 0 { if header.Time <= parent.Time {
return errZeroBlockTime return errZeroBlockTime
} }
// Verify the block's difficulty based in it's timestamp and parent's difficulty // Verify the block's difficulty based in it's timestamp and parent's difficulty
expected := CalcDifficulty(chain, header.Time.Uint64(), parent) expected := CalcDifficulty(chain, header.Time, parent)
if expected.Cmp(header.Difficulty) != 0 { if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected) return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
} }
// Verify that the gas limit is <= 2^63-1 // Verify that the gas limit is <= 2^63-1
if header.GasLimit.Cmp(math.MaxBig63) > 0 { cap := uint64(0x7fffffffffffffff)
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, math.MaxBig63) if header.GasLimit > cap {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
} }
// Verify that the gasUsed is <= gasLimit // Verify that the gasUsed is <= gasLimit
if header.GasUsed.Cmp(header.GasLimit) > 0 { if header.GasUsed > header.GasLimit {
return fmt.Errorf("invalid gasUsed: have %v, gasLimit %v", header.GasUsed, header.GasLimit) return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
} }
// Verify that the gas limit remains within allowed bounds // Verify that the gas limit remains within allowed bounds
diff := new(big.Int).Set(parent.GasLimit) diff := int64(parent.GasLimit) - int64(header.GasLimit)
diff = diff.Sub(diff, header.GasLimit) if diff < 0 {
diff.Abs(diff) diff *= -1
}
limit := parent.GasLimit / params.GasLimitBoundDivisor
limit := new(big.Int).Set(parent.GasLimit) if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
limit = limit.Div(limit, params.GasLimitBoundDivisor) return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
if diff.Cmp(limit) >= 0 || header.GasLimit.Cmp(params.MinGasLimit) < 0 {
return fmt.Errorf("invalid gas limit: have %v, want %v += %v", header.GasLimit, parent.GasLimit, limit)
} }
// Verify that the block number is parent's +1 // Verify that the block number is parent's +1
if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
@ -401,7 +400,7 @@ func CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Head
return calcDifficulty2(chain, parentNumber, parentDiff, parent) return calcDifficulty2(chain, parentNumber, parentDiff, parent)
} else { } else {
// (chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) // (chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header)
return fluxDifficulty(chain, big.NewInt(int64(time)), parentTime, parentNumber, parentDiff, parent) return fluxDifficulty(chain, big.NewInt(int64(time)), big.NewInt(int64(parentTime)), parentNumber, parentDiff, parent)
} }
} }
@ -580,8 +579,15 @@ func fluxDifficulty(chain consensus.ChainReader, time, parentTime, parentNumber,
// VerifySeal implements consensus.Engine, checking whether the given block satisfies // VerifySeal implements consensus.Engine, checking whether the given block satisfies
// the PoW difficulty requirements. // the PoW difficulty requirements.
func (ubqhash *Ubqhash) VerifySeal(chain consensus.ChainReader, header *types.Header) error { func (ubqhash *Ubqhash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
return ubqhash.verifySeal(chain, header, false)
}
// verifySeal checks whether a block satisfies the PoW difficulty requirements,
// either using the usual ethash cache for it, or alternatively using a full DAG
// to make remote mining fast.
func (ubqhash *Ubqhash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error {
// If we're running a fake PoW, accept any seal as valid // If we're running a fake PoW, accept any seal as valid
if ubqhash.fakeMode { if ubqhash.config.PowMode == ModeFake || ubqhash.config.PowMode == ModeFullFake {
time.Sleep(ubqhash.fakeDelay) time.Sleep(ubqhash.fakeDelay)
if ubqhash.fakeFail == header.Number.Uint64() { if ubqhash.fakeFail == header.Number.Uint64() {
return errInvalidPoW return errInvalidPoW
@ -590,30 +596,52 @@ func (ubqhash *Ubqhash) VerifySeal(chain consensus.ChainReader, header *types.He
} }
// If we're running a shared PoW, delegate verification to it // If we're running a shared PoW, delegate verification to it
if ubqhash.shared != nil { if ubqhash.shared != nil {
return ubqhash.shared.VerifySeal(chain, header) return ubqhash.shared.verifySeal(chain, header, fulldag)
}
// Sanity check that the block number is below the lookup table size (60M blocks)
number := header.Number.Uint64()
if number/epochLength >= uint64(len(cacheSizes)) {
// Go < 1.7 cannot calculate new cache/dataset sizes (no fast prime check)
return errNonceOutOfRange
} }
// Ensure that we have a valid difficulty for the block // Ensure that we have a valid difficulty for the block
if header.Difficulty.Sign() <= 0 { if header.Difficulty.Sign() <= 0 {
return errInvalidDifficulty return errInvalidDifficulty
} }
// Recompute the digest and PoW value and verify against the header // Recompute the digest and PoW values
cache := ubqhash.cache(number) number := header.Number.Uint64()
size := datasetSize(number) var (
if ubqhash.tester { digest []byte
size = 32 * 1024 result []byte
)
// If fast-but-heavy PoW verification was requested, use an ethash dataset
if fulldag {
dataset := ubqhash.dataset(number, true)
if dataset.generated() {
digest, result = hashimotoFull(dataset.dataset, ubqhash.SealHash(header).Bytes(), header.Nonce.Uint64())
// Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
// until after the call to hashimotoFull so it's not unmapped while being used.
runtime.KeepAlive(dataset)
} else {
// Dataset not yet generated, don't hang, use a cache instead
fulldag = false
}
} }
digest, result := hashimotoLight(size, cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64()) // If slow-but-light PoW verification was requested (or DAG not yet ready), use an ethash cache
if !fulldag {
cache := ubqhash.cache(number)
size := datasetSize(number)
if ubqhash.config.PowMode == ModeTest {
size = 32 * 1024
}
digest, result = hashimotoLight(size, cache.cache, ubqhash.SealHash(header).Bytes(), header.Nonce.Uint64())
// Caches are unmapped in a finalizer. Ensure that the cache stays alive
// until after the call to hashimotoLight so it's not unmapped while being used.
runtime.KeepAlive(cache)
}
// Verify the calculated values against the ones provided in the header
if !bytes.Equal(header.MixDigest[:], digest) { if !bytes.Equal(header.MixDigest[:], digest) {
return errInvalidMixDigest return errInvalidMixDigest
} }
target := new(big.Int).Div(maxUint256, header.Difficulty) target := new(big.Int).Div(two256, header.Difficulty)
if new(big.Int).SetBytes(result).Cmp(target) > 0 { if new(big.Int).SetBytes(result).Cmp(target) > 0 {
return errInvalidPoW return errInvalidPoW
} }
@ -627,7 +655,7 @@ func (ubqhash *Ubqhash) Prepare(chain consensus.ChainReader, header *types.Heade
if parent == nil { if parent == nil {
return consensus.ErrUnknownAncestor return consensus.ErrUnknownAncestor
} }
header.Difficulty = CalcDifficulty(chain, header.Time.Uint64(), parent) header.Difficulty = CalcDifficulty(chain, header.Time, parent)
return nil return nil
} }
@ -650,6 +678,29 @@ var (
big32 = big.NewInt(32) big32 = big.NewInt(32)
) )
// SealHash returns the hash of a block prior to it being sealed.
func (ubqhash *Ubqhash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
rlp.Encode(hasher, []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra,
})
hasher.Sum(hash[:0])
return hash
}
// AccumulateRewards credits the coinbase of the given block with the mining // AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for // reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded. // included uncles. The coinbase of each uncle block is also rewarded.