Skip to content

Commit

Permalink
Add HF3 and Cuckarooz logic (#17)
Browse files Browse the repository at this point in the history
* Add Cuckarooz

* Equivalent of mimblewimble/grin#3343
  • Loading branch information
quentinlesceller authored Jun 15, 2020
1 parent 7bef227 commit 6aac67d
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 51 deletions.
36 changes: 31 additions & 5 deletions core/consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,49 @@ const FloonetFirstHardFork uint64 = 185040
// FloonetSecondHardFork is the Floonet second hard fork height, set to happen around 2019-12-19
const FloonetSecondHardFork uint64 = 298080

// FloonetThirdHardFork is the Floonet second hard fork height, set to happen around 2020-06-20
const FloonetThirdHardFork uint64 = 552960

// TestingFirstHardFork is the AutomatedTesting and UserTesting HF1 height
const TestingFirstHardFork uint64 = 3

// TestingSecondHardFork is the AutomatedTesting and UserTesting HF2 height
const TestingSecondHardFork uint64 = 6

// TestingThirdHardFork is the AutomatedTesting and UserTesting HF3 height
const TestingThirdHardFork uint64 = 9

// HeaderVersion compute possible block version at a given height, implements
// 6 months interval scheduled hard forks for the first 2 years.
func HeaderVersion(chainType ChainType, height uint64) uint16 {
hfInterval := uint16(1 + height/HardForkInterval)
switch chainType {
case Mainnet:
return hfInterval
case Floonet:
if height < FloonetFirstHardFork {
return 1
} else if height < FloonetSecondHardFork {
return 2
} else if height < 3*HardForkInterval {
} else if height < FloonetThirdHardFork {
return 3
} else if height < 3*HardForkInterval {
return 4
} else {
return hfInterval
}
// everything else just like mainnet
case AutomatedTesting, UserTesting:
if height < TestingFirstHardFork {
return 1
} else if height < TestingSecondHardFork {
return 2
} else if height < TestingThirdHardFork {
return 3
} else if height < 4*HardForkInterval {
return 4
} else {
return 5
}
default:
return hfInterval
}
Expand All @@ -149,7 +176,7 @@ func HeaderVersion(chainType ChainType, height uint64) uint16 {
// ValidHeaderVersion check whether the block version is valid at a given height, implements
// 6 months interval scheduled hard forks for the first 2 years.
func ValidHeaderVersion(chainType ChainType, height uint64, version uint16) bool {
return height < 3*HardForkInterval && version == HeaderVersion(chainType, height)
return height < 4*HardForkInterval && version == HeaderVersion(chainType, height)
}

// DifficultyAdjustWindow is the number of blocks used to calculate difficulty adjustments
Expand All @@ -169,8 +196,7 @@ const DifficultyDampFactor uint64 = 3
const ARScaleDampFactor uint64 = 13

// GraphWeight compute weight of a graph as number of siphash bits defining the graph
// Must be made dependent on height to phase out C31 in early 2020
// Later phase outs are on hold for now
// The height dependence allows a 30-week linear transition from C31+ to C32+ starting after 1 year
func GraphWeight(chainType ChainType, height uint64, edgeBits uint8) uint64 {
xprEdgeBits := uint64(edgeBits)
expiryHeight := YearHeight
Expand Down
79 changes: 45 additions & 34 deletions core/consensus/consensus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,28 +98,34 @@ func TestSecondaryPoWRatio(t *testing.T) {
assert.Equal(t, SecondaryPoWRatio(twoYears+1), uint64(0))
}

func TestValidHeaderVersion(t *testing.T) {
func TestHardForks(t *testing.T) {
// Tests for Mainnet
{
assert.True(t, ValidHeaderVersion(Mainnet, YearHeight/2, 2))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight/2, 1))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight, 1))
assert.True(t, ValidHeaderVersion(Mainnet, YearHeight/2+1, 2))
assert.True(t, ValidHeaderVersion(Mainnet, YearHeight/2-1, 1))

assert.True(t, ValidHeaderVersion(Mainnet, YearHeight-1, 2))
assert.True(t, ValidHeaderVersion(Mainnet, YearHeight, 3))
assert.True(t, ValidHeaderVersion(Mainnet, YearHeight+1, 3))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight, 2))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 2))

// v4 not active yet
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 4))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 3))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 2))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2, 1))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*2, 3))
assert.False(t, ValidHeaderVersion(Mainnet, YearHeight*3/2+1, 3))
assert.True(t, ValidHeaderVersion(Mainnet, 0, 1))
assert.True(t, ValidHeaderVersion(Mainnet, 10, 1))
assert.False(t, ValidHeaderVersion(Mainnet, 10, 2))

assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval-1, 1))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval, 1))
assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval, 2))
assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval+1, 2))

assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*2-1, 2))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*2, 2))
assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*2, 3))
assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*2+1, 3))

assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*3-1, 3))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*3, 3))
assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*3, 4))
assert.True(t, ValidHeaderVersion(Mainnet, HardForkInterval*3+1, 4))

// v5 not active yet
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 5))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 4))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 3))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 2))
assert.False(t, ValidHeaderVersion(Mainnet, HardForkInterval*4, 1))
}
// Tests for Floonet
{
Expand All @@ -132,25 +138,30 @@ func TestValidHeaderVersion(t *testing.T) {
assert.True(t, ValidHeaderVersion(Floonet, FloonetFirstHardFork+1, 2))
assert.False(t, ValidHeaderVersion(Floonet, FloonetFirstHardFork, 1))

assert.False(t, ValidHeaderVersion(Floonet, YearHeight, 1))

assert.True(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork-1, 2))
assert.True(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork, 3))
assert.True(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork+1, 3))
assert.False(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork, 2))
assert.False(t, ValidHeaderVersion(Floonet, FloonetSecondHardFork, 1))

assert.False(t, ValidHeaderVersion(Floonet, YearHeight-1, 2))
assert.True(t, ValidHeaderVersion(Floonet, YearHeight-1, 3))
assert.True(t, ValidHeaderVersion(Floonet, YearHeight, 3))
assert.True(t, ValidHeaderVersion(Floonet, YearHeight+1, 3))

// v4 not active yet
assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 4))
assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 3))
assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 2))
assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2, 1))
assert.False(t, ValidHeaderVersion(Floonet, YearHeight*2, 3))
assert.False(t, ValidHeaderVersion(Floonet, YearHeight*3/2+1, 3))
assert.True(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork-1, 3))
assert.True(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 4))
assert.True(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork+1, 4))
assert.False(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 3))
assert.False(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 2))
assert.False(t, ValidHeaderVersion(Floonet, FloonetThirdHardFork, 1))

assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*2-1, 1))
assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*2-1, 2))
assert.True(t, ValidHeaderVersion(Floonet, HardForkInterval*2-1, 3))
assert.True(t, ValidHeaderVersion(Floonet, HardForkInterval*2, 3))
assert.True(t, ValidHeaderVersion(Floonet, HardForkInterval*2+1, 3))

// v5 not active yet
assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 5))
assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 4))
assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 3))
assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 2))
assert.False(t, ValidHeaderVersion(Floonet, HardForkInterval*4, 1))
}
}
4 changes: 4 additions & 0 deletions core/pow.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func createPoWContext(chainType consensus.ChainType, height uint64, edgeBits uin
// Mainnet has Cuckaroo29 for AR and Cuckatoo30+ for AF
case consensus.Mainnet <= chainType && edgeBits > 29:
return pow.NewCuckatooCtx(chainType, edgeBits, proofSize, maxSols)
case consensus.Mainnet <= chainType && consensus.ValidHeaderVersion(chainType, height, 4):
return pow.NewCuckaroozCtx(chainType, edgeBits, proofSize)
case consensus.Mainnet <= chainType && consensus.ValidHeaderVersion(chainType, height, 3):
return pow.NewCuckaroomCtx(chainType, edgeBits, proofSize)
case consensus.Mainnet <= chainType && consensus.ValidHeaderVersion(chainType, height, 2):
Expand All @@ -34,6 +36,8 @@ func createPoWContext(chainType consensus.ChainType, height uint64, edgeBits uin
return pow.NewCuckarooCtx(chainType, edgeBits, proofSize)
case consensus.Floonet <= chainType && edgeBits > 29:
return pow.NewCuckatooCtx(chainType, edgeBits, proofSize, maxSols)
case consensus.Floonet <= chainType && consensus.ValidHeaderVersion(chainType, height, 4):
return pow.NewCuckaroozCtx(chainType, edgeBits, proofSize)
case consensus.Floonet <= chainType && consensus.ValidHeaderVersion(chainType, height, 3):
return pow.NewCuckaroomCtx(chainType, edgeBits, proofSize)
case consensus.Floonet <= chainType && consensus.ValidHeaderVersion(chainType, height, 2):
Expand Down
5 changes: 3 additions & 2 deletions core/pow/cuckaroo.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (c *CuckarooContext) Verify(proof Proof) error {
nonces := proof.Nonces
uvs := make([]uint64, 2*proof.proofSize())
var xor0, xor1 uint64
nodeMask := c.params.edgeMask

for n := 0; n < proof.proofSize(); n++ {
if nonces[n] > c.params.edgeMask {
Expand All @@ -58,9 +59,9 @@ func (c *CuckarooContext) Verify(proof Proof) error {
}
// 21 is standard siphash rotation constant
edge := SipHashBlock(c.params.siphashKeys, nonces[n], 21, false)
uvs[2*n] = edge & c.params.edgeMask
uvs[2*n+1] = (edge >> 32) & c.params.edgeMask
uvs[2*n] = edge & nodeMask
xor0 ^= uvs[2*n]
uvs[2*n+1] = (edge >> 32) & nodeMask
xor1 ^= uvs[2*n+1]
}

Expand Down
7 changes: 4 additions & 3 deletions core/pow/cuckarood.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (c *CuckaroodContext) Verify(proof Proof) error {
uvs := make([]uint64, 2*proof.proofSize())
ndir := make([]uint64, 2)
var xor0, xor1 uint64
nodemask := c.params.edgeMask >> 1
nodeMask := c.params.edgeMask >> 1

for n := 0; n < proof.proofSize(); n++ {
dir := uint(nonces[n] & 1)
Expand All @@ -62,11 +62,12 @@ func (c *CuckaroodContext) Verify(proof Proof) error {
if n > 0 && nonces[n] <= nonces[n-1] {
return errors.New("edges not ascending")
}
// cuckarood uses a non-standard siphash rotation constant 25 as anti-ASIC tweak
edge := SipHashBlock(c.params.siphashKeys, nonces[n], 25, false)
idx := 4*ndir[dir] + 2*uint64(dir)
uvs[idx] = edge & nodemask
uvs[idx+1] = (edge >> 32) & nodemask
uvs[idx] = edge & nodeMask
xor0 ^= uvs[idx]
uvs[idx+1] = (edge >> 32) & nodeMask
xor1 ^= uvs[idx+1]
ndir[dir]++
}
Expand Down
15 changes: 8 additions & 7 deletions core/pow/cuckaroom.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ func (c *CuckaroomContext) Verify(proof Proof) error {
return errors.New("wrong cycle length")
}
nonces := proof.Nonces
from := make([]uint32, proof.proofSize())
to := make([]uint32, proof.proofSize())
var xorFrom uint32 = 0
var xorTo uint32 = 0
nodemask := c.params.edgeMask >> 1
from := make([]uint64, proof.proofSize())
to := make([]uint64, proof.proofSize())
var xorFrom uint64 = 0
var xorTo uint64 = 0
nodeMask := c.params.edgeMask >> 1

for n := 0; n < proof.proofSize(); n++ {
if nonces[n] > c.params.edgeMask {
Expand All @@ -59,10 +59,11 @@ func (c *CuckaroomContext) Verify(proof Proof) error {
if n > 0 && nonces[n] <= nonces[n-1] {
return errors.New("edges not ascending")
}
// 21 is standard siphash rotation constant
edge := SipHashBlock(c.params.siphashKeys, nonces[n], 21, true)
from[n] = uint32(edge & nodemask)
from[n] = edge & nodeMask
xorFrom ^= from[n]
to[n] = uint32((edge >> 32) & nodemask)
to[n] = (edge >> 32) & nodeMask
xorTo ^= to[n]
}
if xorFrom != xorTo {
Expand Down
104 changes: 104 additions & 0 deletions core/pow/cuckarooz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2020 BlockCypher
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pow

import (
"errors"

"github.com/blockcypher/libgrin/core/consensus"
)

// NewCuckaroozCtx instantiates a new CuckaroozContext as a PowContext. Note that this can't
/// be moved in the PoWContext trait as this particular trait needs to be
/// convertible to an object trait.
func NewCuckaroozCtx(chainType consensus.ChainType, edgeBits uint8, proofSize int) *CuckaroomContext {
cp := new(CuckooParams)
params := cp.new(edgeBits, proofSize)
return &CuckaroomContext{chainType, params}
}

// CuckaroozContext is a Cuckarooz cycle context. Only includes the verifier for now.
type CuckaroozContext struct {
chainType consensus.ChainType
params CuckooParams
}

// SetHeaderNonce sets the header nonce.
func (c *CuckaroozContext) SetHeaderNonce(header []uint8, nonce *uint32) {
c.params.resetHeaderNonce(header, nonce)
}

// Verify verifies the Cuckaroom context.
func (c *CuckaroozContext) Verify(proof Proof) error {
if proof.proofSize() != consensus.ChainTypeProofSize(c.chainType) {
return errors.New("wrong cycle length")
}
nonces := proof.Nonces
uvs := make([]uint64, 2*proof.proofSize())
var xoruv uint64 = 0
nodeMask := c.params.edgeMask<<1 | 1

for n := 0; n < proof.proofSize(); n++ {
if nonces[n] > c.params.edgeMask {
return errors.New("edge too big")
}
if n > 0 && nonces[n] <= nonces[n-1] {
return errors.New("edges not ascending")
}
// 21 is standard siphash rotation constant
edge := SipHashBlock(c.params.siphashKeys, nonces[n], 21, true)
uvs[2*n] = edge & nodeMask
uvs[2*n+1] = edge >> 32 & nodeMask
xoruv ^= uvs[2*n] ^ uvs[2*n+1]
}
if xoruv != 0 {
return errors.New("endpoints don't match up")
}

n := 0
i := 0
j := 0
for {
// follow cycle
j = i
k := j
for {
k = (j + 1) % (2 * c.params.proofSize)
if k == i {
break
}
}
if uvs[k] == uvs[i] {
// find other edge endpoint matching one at i
if j != i {
return errors.New("branch in cycle")
}
j = k
}
if j == i {
return errors.New("cycle dead ends")
}
i = j ^ 1
n++
if i == 0 {
break
}
}
if n == c.params.proofSize {
return nil
}
return errors.New("cycle too short")

}
Loading

0 comments on commit 6aac67d

Please sign in to comment.