Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding SkipchainDB.GetProofFromIndex #2423

Merged
merged 2 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion byzcoin/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func TestService_AddTransaction_WrongNode(t *testing.T) {
// force the synchronization as the new node needs to get the
// propagation to know about the skipchain but we're not testing that
// here
proof, err := b.Services[0].db().GetProof(b.Genesis.Hash)
proof, err := b.Services[0].db().GetProofForLatest(b.Genesis.Hash)
require.NoError(t, err)
_, err = outside.db().StoreBlocks(proof)
require.NoError(t, err)
Expand Down
48 changes: 10 additions & 38 deletions skipchain/skipchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,43 +681,15 @@ func (s *Service) GetSingleBlock(id *GetSingleBlock) (*SkipBlock, error) {
// GetSingleBlockByIndex searches for the given block and returns it. If no such block is
// found, a nil is returned.
func (s *Service) GetSingleBlockByIndex(id *GetSingleBlockByIndex) (*GetSingleBlockByIndexReply, error) {
sb := s.db.GetByID(id.Genesis)
if sb == nil {
return nil, errors.New("No such genesis-block")
}
links := []*ForwardLink{{
To: id.Genesis,
NewRoster: sb.Roster,
}}
if sb.Index == id.Index {
return &GetSingleBlockByIndexReply{sb, links}, nil
}
for len(sb.ForwardLink) > 0 {
// Search for the highest ForwardLink that doesn't shoot over the target
sb = func() *SkipBlock {
for i := len(sb.ForwardLink) - 1; i >= 0; i-- {
to := sb.ForwardLink[i].To
// We can have holes in the forward links
if to != nil {
tmp := s.db.GetByID(to)
if tmp != nil && tmp.Index <= id.Index {
links = append(links, sb.ForwardLink[i])
return tmp
}
}
}
return nil
}()
if sb == nil {
return nil, errors.New("didn't find block in forward link")
}
if sb.Index == id.Index {
return &GetSingleBlockByIndexReply{sb, links}, nil
}
pr, err := s.db.GetProofFromIndex(id.Genesis, id.Index)
if err != nil {
return nil, xerrors.Errorf("couldn't get path to block: %v", err)
}
links, err := pr.GetForwardLinks()
if err != nil {
return nil, xerrors.Errorf("couldn't get forward-links: %v", err)
}
err := fmt.Errorf("no block with index \"%d\" found", id.Index)
log.Error(s.ServerIdentity(), err)
return nil, err
return &GetSingleBlockByIndexReply{pr[len(pr)-1], links}, nil
}

// GetAllSkipchains currently returns a list of all the known blocks.
Expand Down Expand Up @@ -1163,7 +1135,7 @@ func (s *Service) forwardLinkLevel0(src, dst *SkipBlock) error {

// We send the shortest chain to the new conodes to let
// them know they joined the cothority
proof, err := s.db.GetProof(src.SkipChainID())
proof, err := s.db.GetProofForLatest(src.SkipChainID())
if err != nil {
return err
}
Expand Down Expand Up @@ -1647,7 +1619,7 @@ func (s *Service) propagateForwardLinkHandler(msg network.Message) error {
// PropagateProof is a simple function that will build the proof of a given
// skipchain and send it the given roster.
func (s *Service) PropagateProof(roster *onet.Roster, sid SkipBlockID) error {
proof, err := s.db.GetProof(sid)
proof, err := s.db.GetProofForLatest(sid)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion skipchain/skipchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ func createSkipchain(service *Service, ro *onet.Roster) (Proof, error) {
}
}

blocks, err := service.db.GetProof(sbRoot.SkipChainID())
blocks, err := service.db.GetProofForLatest(sbRoot.SkipChainID())
return Proof(blocks), err
}

Expand Down
188 changes: 104 additions & 84 deletions skipchain/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,11 @@ func (sb *SkipBlock) GetForwardLen() int {

// pathForIndex computes the highest height that can be used to go
// to the targeted index and also returns the index associated. It
// works for forward abd backward links.
// works for forward and backward links.
func (sb *SkipBlock) pathForIndex(targetIndex int) (int, int) {
if sb.BaseHeight == 1 {
return 0, sb.Index + 1
}
diff := math.Log(math.Abs(float64(targetIndex - sb.Index)))
base := math.Log(float64(sb.BaseHeight))
// Highest forward or backward link height that can be followed.
Expand Down Expand Up @@ -602,6 +605,32 @@ func (sbs Proof) VerifyFromID(id SkipBlockID) error {
return sbs.verifyChain()
}

// GetForwardLinks creates a slice of ForwardLinks of the proof,
// starting with a forwardlink pointing out of nil to the genesis block to
// give the roster.
func (sbs Proof) GetForwardLinks() (links []*ForwardLink, err error) {
if len(sbs) == 0 {
return nil, xerrors.New("cannot create links for empty proof")
}
links = make([]*ForwardLink, len(sbs))
links[0] = &ForwardLink{
To: sbs[0].Hash,
NewRoster: sbs[0].Roster,
}

logBH := math.Log(float64(sbs[0].BaseHeight))
for i, sb := range sbs[:len(sbs)-1] {
logDist := math.Log(float64(sbs[i+1].Index - sb.Index))
// Using math.Round here to ignore rounding errors in math.Log
height := int(math.Round(logDist / logBH))
if len(sb.ForwardLink) <= height || sb.ForwardLink[height].IsEmpty() {
return nil, xerrors.New("missing forward-link in proof")
}
links[i+1] = sb.ForwardLink[height]
}
return
}

func (sbs Proof) verifyChain() error {
for i, sb := range sbs {
if !sb.CalculateHash().Equal(sb.Hash) {
Expand Down Expand Up @@ -1148,113 +1177,104 @@ func (db *SkipBlockDB) GetFuzzy(id string) (*SkipBlock, error) {
return sb, err
}

// GetProof returns the shortest chain from the genesis to the latest block
// using the heighest forward-links available in the local db
func (db *SkipBlockDB) GetProof(sid SkipBlockID) (sbs []*SkipBlock, err error) {
sbs = make([]*SkipBlock, 0)

// GetProofFromIndex returns the shortest chain from the genesis to the block
// with index dest using the highest forward-links available in the local db.
// If dest < 0, search up to the latest block.
func (db *SkipBlockDB) GetProofFromIndex(sid SkipBlockID,
dest int) (pr Proof, err error) {
err = db.View(func(tx *bbolt.Tx) error {
sb, err := db.getFromTx(tx, sid)
if err != nil {
return err
}
if sb == nil {
return xerrors.New("didn't find genesis block")
}

sbs = append(sbs, sb)

for sb != nil && len(sb.ForwardLink) > 0 {
// In some cases, a forward-link could be stored before the
// actual target block is so we go as far as we can when
// following the forward-links.
links := sb.ForwardLink
for k := len(links) - 1; k >= 0; k-- {
fl := links[k]
if fl == nil || fl.IsEmpty() {
continue
}

sb, err = db.getFromTx(tx, fl.To)

if err != nil {
return err
}

if sb != nil {
k = -1
}
}

if sb == nil {
// The very latest block could still in processing but the
// forward-link level 0 is already stored.
return nil
}

// One way to insure there is no corrupted forward-link is
// to insure the index is monotonically increasing.
if sb.Index <= sbs[len(sbs)-1].Index {
return ErrorInconsistentForwardLink
}

sbs = append(sbs, sb)
pr, err = db.getPath(tx, sb, dest)
if err != nil {
return xerrors.Errorf("couldn't get path: %v", err)
}

return err
return nil
})
return
}

// GetProofForID returns the shortest chain known from the genesis to the given
// block using the highest forward-links available in the local db.
func (db *SkipBlockDB) GetProofForID(bid SkipBlockID) (sbs Proof, err error) {
err = db.View(func(tx *bbolt.Tx) error {
target, err := db.getFromTx(tx, bid)
if err != nil {
return err
}
if target == nil {
return errors.New("couldn't find the block")
}
// Iterate over all blocks until it's either the last block or the
// one required by the call.
func (db *SkipBlockDB) getPath(tx *bbolt.Tx, sb *SkipBlock,
dest int) (pr Proof, err error) {
pr = append(pr, sb)
if dest == 0 {
return
}

sb, err := db.getFromTx(tx, target.SkipChainID())
for len(sb.ForwardLink) > 0 {
sb, err = db.getHighestJump(tx, sb, dest)
if err != nil {
return err
return nil, xerrors.Errorf("while fetching next jump: %v", err)
}
if sb == nil {
// It should never happen if the previous is found.
return errors.New("couldn't find the genesis block")
pr = append(pr, sb)
if sb.Index == dest {
return
}
}

sbs = append(sbs, sb)

for !sb.Hash.Equal(bid) && len(sb.ForwardLink) > 0 {
diff := math.Log(float64(target.Index - sb.Index))
base := math.Log(float64(sb.BaseHeight))
maxHeight := 0
if base != 0 {
maxHeight = int(math.Min(diff/base, float64(len(sb.ForwardLink)-1)))
}
// When looking for latest block, it's normal that there is no match found.
if dest == -1 {
return
}
return nil, xerrors.New("didn't find destination block")
}

id := sb.ForwardLink[maxHeight].To
sb, err = db.getFromTx(tx, id)
if err != nil {
return err
}
// Search for the closest block that is reachable before or at the destination.
// dest < 0 indicates to chose the highest forward-link.
func (db *SkipBlockDB) getHighestJump(tx *bbolt.Tx, start *SkipBlock,
dest int) (*SkipBlock, error) {
for i := len(start.ForwardLink) - 1; i >= 0; i-- {
// We can have holes in the forward links
if start.ForwardLink[i].IsEmpty() {
continue
}
sb, err := db.getFromTx(tx, start.ForwardLink[i].To)
if err != nil {
return nil, xerrors.Errorf("while fetching block from db: %v",
err)
}

if sb == nil {
return errors.New("couldn't find one of the blocks")
if sb != nil {
if dest < 0 {
dest = sb.Index
}

if sb.Index <= sbs[len(sbs)-1].Index {
return ErrorInconsistentForwardLink
if sb.Index <= dest {
return sb, nil
}

sbs = append(sbs, sb)
}
}
return nil, xerrors.New("didn't find any appropriate block")
}

return nil
})
// GetProofForLatest returns the shortest chain from the genesis to the latest block
// using the highest forward-links available in the local db
func (db *SkipBlockDB) GetProofForLatest(sid SkipBlockID) (Proof, error) {
return db.GetProofFromIndex(sid, -1)
}

return
// GetProof for API compatibility
// Deprecated: please use GetProofForLatest(sid).
func (db *SkipBlockDB) GetProof(sid SkipBlockID) (Proof, error) {
return db.GetProofFromIndex(sid, -1)
}

// GetProofForID returns the shortest known chain from the genesis to the given
// block using the highest forward-links available in the local db.
func (db *SkipBlockDB) GetProofForID(bid SkipBlockID) (Proof, error) {
sb := db.GetByID(bid)
if sb == nil {
return nil, xerrors.New("couldn't find block")
}
return db.GetProofFromIndex(sb.SkipChainID(), sb.Index)
}

// GetSkipchains returns all latest skipblocks from all skipchains.
Expand Down
45 changes: 38 additions & 7 deletions skipchain/struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,14 @@ func TestSkipBlock_PathForIndex(t *testing.T) {
{0, 6, 2, 31, 16},
{0, 1, 2, 0, 0},
{1, 1, 2, 1, 1},
// bigger numbers
{1 << 24, 12, 4, 1<<24 + 1<<20, 1<<24 + 1<<20},
{1 << 24, 12, 4, 1<<24 + 1<<20 - 1, 1<<24 + 1<<18},
// backwards test
{32, 6, 2, 0, 0},
{32, 6, 2, 1, 16},
// base 1 test
{0, 1, 1, 2, 1},
}

for _, v := range vectors {
Expand All @@ -355,7 +360,7 @@ func TestSkipBlock_PathForIndex(t *testing.T) {

// This checks if the it returns the shortest path or an error
// when blocks are missing
func TestSkipBlockDB_GetProof(t *testing.T) {
func TestSkipBlockDB_GetProofFromIndex(t *testing.T) {
local := onet.NewLocalTest(suite)
_, ro, _ := local.GenTree(2, false)
defer local.CloseAll()
Expand Down Expand Up @@ -392,10 +397,37 @@ func TestSkipBlockDB_GetProof(t *testing.T) {
require.NoError(t, root.ForwardLink[0].sign(ro))
require.NoError(t, root.ForwardLink[1].sign(ro))

_, err := db.StoreBlocks([]*SkipBlock{root, sb1, sb2})
blockIDs, err := db.StoreBlocks([]*SkipBlock{root, sb1, sb2})
require.NoError(t, err)

blocks, err := db.GetProof(root.Hash)
tests := []struct {
dest int
links int
blocks int
}{{0, 1, 1},
{1, 2, 2},
{2, 2, 2},
{-1, 2, 2}}

for _, test := range tests {
blocks, err := db.GetProofFromIndex(root.Hash, test.dest)
require.NoError(t, err)
require.Equal(t, test.blocks, len(blocks))
require.True(t, blocks[0].Equal(root))
latestBlock := test.dest
if test.dest == -1 {
latestBlock = 2
}
require.True(t, blocks[len(blocks)-1].Hash.Equal(blockIDs[latestBlock]))

links, err := blocks.GetForwardLinks()
require.NoError(t, err)
require.Equal(t, test.links, len(links))
require.True(t, links[0].To.Equal(root.Hash))
require.True(t, links[len(links)-1].To.Equal(blockIDs[latestBlock]))
}

blocks, err := db.GetProofForLatest(root.Hash)
require.NoError(t, err)
require.Equal(t, 2, len(blocks))
require.True(t, blocks[1].Hash.Equal(sb2.Hash))
Expand All @@ -410,10 +442,9 @@ func TestSkipBlockDB_GetProof(t *testing.T) {
})
require.NoError(t, err)

// last block is missing so it should return only until sb1.
bb, err := db.GetProof(root.Hash)
require.NoError(t, err)
require.Equal(t, 2, len(bb))
// last block is missing so it should return an error.
_, err = db.GetProofForLatest(root.Hash)
require.Error(t, err)

_, err = db.GetProofForID(sb2.Hash)
require.Error(t, err)
Expand Down