Skip to content

Commit

Permalink
accumulator/undo: Add serialization for undoblocks
Browse files Browse the repository at this point in the history
UndoBlocks need to be saved and re-accessed whenever there is a reorg
with the Bitcoin blockchain. Serialization and Deserialization is
added so that these blocks can be saved and fetched from disk.

UndoBlocks are also now exported so that callers will be able to access
the new Serialize() and Deserialize() methods.
  • Loading branch information
kcalvinalvin committed Aug 7, 2021
1 parent fc7a3c4 commit dca867c
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 7 deletions.
6 changes: 4 additions & 2 deletions accumulator/forest.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ func (f *Forest) addv2(adds []Leaf) {
// Note that this does not modify in place! All deletes occur simultaneous with
// adds, which show up on the right.
// Also, the deletes need there to be correct proof data, so you should first call Verify().
func (f *Forest) Modify(adds []Leaf, delsUn []uint64) (*undoBlock, error) {
func (f *Forest) Modify(adds []Leaf, delsUn []uint64) (*UndoBlock, error) {
numdels, numadds := len(delsUn), len(adds)
delta := int64(numadds - numdels) // watch 32/64 bit
if int64(f.numLeaves)+delta < 0 {
Expand Down Expand Up @@ -463,7 +463,9 @@ func (f *Forest) reMap(destRows uint8) error {
return fmt.Errorf("changing by more than 1 not programmed yet")
}

fmt.Printf("remap forest %d rows -> %d rows\n", f.rows, destRows)
if verbose {
fmt.Printf("remap forest %d rows -> %d rows\n", f.rows, destRows)
}

// for row reduction
if destRows < f.rows {
Expand Down
102 changes: 97 additions & 5 deletions accumulator/undo.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package accumulator

import (
"encoding/binary"
"fmt"
"io"
)

/* we need to be able to undo blocks! for bridge nodes at least.
Expand All @@ -13,14 +15,14 @@ although actually it can make sense for non-bridge nodes to undo as well...

// blockUndo is all the data needed to undo a block: number of adds,
// and all the hashes that got deleted and where they were from
type undoBlock struct {
type UndoBlock struct {
numAdds uint32 // number of adds in the block
positions []uint64 // position of all deletions this block
hashes []Hash // hashes that were deleted
}

// ToString returns a string
func (u *undoBlock) ToString() string {
func (u *UndoBlock) ToString() string {
s := fmt.Sprintf("- uuuu undo block %d adds\t", u.numAdds)
s += fmt.Sprintf("%d dels:\t", len(u.positions))
if len(u.positions) != len(u.hashes) {
Expand All @@ -34,8 +36,98 @@ func (u *undoBlock) ToString() string {
return s
}

// SerializeSize returns how many bytes it would take to serialize this undoblock.
func (u *UndoBlock) SerializeSize() int {
// Size of u.numAdds + len(u.positions) + each position takes up 8 bytes
size := 4 + 8 + (len(u.positions) * 8)

// Size of len(u.hashes) + each hash takes up 32 bytes
size += 8 + (len(u.hashes) * 32)

return size
}

// Serialize encodes the undoblock into the given writer.
func (u *UndoBlock) Serialize(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, u.numAdds)
if err != nil {
return err
}

err = binary.Write(w, binary.BigEndian, uint64(len(u.positions)))
if err != nil {
return err
}

err = binary.Write(w, binary.BigEndian, u.positions)
if err != nil {
return err
}

err = binary.Write(w, binary.BigEndian, uint64(len(u.hashes)))
if err != nil {
return err
}

for _, hash := range u.hashes {
n, err := w.Write(hash[:])
if err != nil {
return err
}

if n != 32 {
err := fmt.Errorf("UndoBlock Serialize supposed to write 32 bytes but wrote %d bytes", n)
return err
}
}

return nil
}

// Deserialize decodes an undoblock from the reader.
func (u *UndoBlock) Deserialize(r io.Reader) error {
err := binary.Read(r, binary.BigEndian, &u.numAdds)
if err != nil {
return err
}

var posCount uint64
err = binary.Read(r, binary.BigEndian, &posCount)
if err != nil {
return err
}
u.positions = make([]uint64, posCount)

err = binary.Read(r, binary.BigEndian, u.positions)
if err != nil {
return err
}

var hashCount uint64
err = binary.Read(r, binary.BigEndian, &hashCount)
if err != nil {
return err
}

u.hashes = make([]Hash, hashCount)

for i := uint64(0); i < hashCount; i++ {
n, err := r.Read(u.hashes[i][:])
if err != nil {
return err
}

if n != 32 {
err := fmt.Errorf("UndoBlock Deserialize supposed to read 32 bytes but read %d bytes", n)
return err
}
}

return nil
}

// Undo reverts a Modify() with the given undoBlock.
func (f *Forest) Undo(ub undoBlock) error {
func (f *Forest) Undo(ub UndoBlock) error {
prevAdds := uint64(ub.numAdds)
prevDels := uint64(len(ub.hashes))
// how many leaves were there at the last block?
Expand Down Expand Up @@ -104,8 +196,8 @@ func (f *Forest) Undo(ub undoBlock) error {
}

// BuildUndoData makes an undoBlock from the same data that you'd give to Modify
func (f *Forest) BuildUndoData(numadds uint64, dels []uint64) *undoBlock {
ub := new(undoBlock)
func (f *Forest) BuildUndoData(numadds uint64, dels []uint64) *UndoBlock {
ub := new(UndoBlock)
ub.numAdds = uint32(numadds)

ub.positions = dels // the deletion positions, in sorted order
Expand Down
103 changes: 103 additions & 0 deletions accumulator/undo_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,114 @@
package accumulator

import (
"bytes"
"fmt"
"math/rand"
"testing"
)

func TestUndoSerializeDeserialize(t *testing.T) {
tests := []struct {
name string
undo UndoBlock
serialized []byte
}{
{
name: "testnet3 block 412",
undo: UndoBlock{
numAdds: 6,
positions: []uint64{455, 459, 461, 464},
hashes: []Hash{
Hash{
0x12, 0x53, 0x6b, 0x08, 0x63, 0x1b, 0x00, 0x3c,
0xa7, 0x97, 0xc3, 0x94, 0x33, 0x3b, 0x7f, 0x98,
0xd5, 0x4a, 0x7d, 0x36, 0x3c, 0x11, 0xc7, 0x44,
0x92, 0x60, 0x72, 0xa1, 0xad, 0xba, 0x20, 0x0b,
},
Hash{
0xf9, 0x0d, 0xea, 0x08, 0xc2, 0xcc, 0x6b, 0x71,
0x54, 0xe4, 0x38, 0x94, 0x40, 0xbc, 0x37, 0xc0,
0xc0, 0x99, 0x05, 0x11, 0x0d, 0x68, 0xd0, 0xf9,
0x57, 0x43, 0x0f, 0xac, 0x98, 0xe4, 0x59, 0x30,
},
Hash{
0xf0, 0xe2, 0xdc, 0x5e, 0xc0, 0x2e, 0x8f, 0x67,
0x32, 0xd4, 0x06, 0x4e, 0xc9, 0xb6, 0x73, 0x86,
0x62, 0xaf, 0xb0, 0x93, 0x69, 0xea, 0x9d, 0xfb,
0xa9, 0x05, 0x98, 0x11, 0x87, 0x05, 0x43, 0x85,
},
Hash{
0x88, 0x66, 0x53, 0xab, 0xd5, 0x16, 0x0f, 0xc5,
0x91, 0x9e, 0x0e, 0x38, 0x5c, 0x0b, 0x83, 0x7e,
0x97, 0xfb, 0xb9, 0x16, 0xed, 0x0a, 0x0d, 0xb6,
0x20, 0xba, 0x6b, 0xfc, 0x2b, 0xbb, 0x7c, 0xff,
},
},
},
},
{
name: "testnet3 block 450",
undo: UndoBlock{
numAdds: 3,
positions: []uint64{454, 474},
hashes: []Hash{
Hash{
0x10, 0xd1, 0x0a, 0xf8, 0xf2, 0x0b, 0x14, 0xc6,
0x12, 0xb1, 0x77, 0xa5, 0xaf, 0xd5, 0x44, 0x76,
0x0f, 0xf5, 0xc4, 0x96, 0x18, 0xac, 0x91, 0x1c,
0xa2, 0x43, 0xf4, 0x19, 0xf9, 0x31, 0xe1, 0x24,
},
Hash{
0x0a, 0xb5, 0x31, 0x83, 0xf6, 0x1f, 0xdb, 0xf0,
0xd1, 0x3d, 0x03, 0xd4, 0x4b, 0x44, 0x14, 0x0f,
0xc3, 0x89, 0x9a, 0x28, 0x25, 0xf6, 0xc5, 0x8e,
0xe7, 0x18, 0x69, 0x90, 0xe0, 0x85, 0xae, 0xfb,
},
},
},
},
}

for _, test := range tests {
beforeSize := test.undo.SerializeSize()
buf := make([]byte, 0, beforeSize)
w := bytes.NewBuffer(buf)
err := test.undo.Serialize(w)
if err != nil {
t.Fatal(err)
}

serializedBytes := w.Bytes()

beforeBytes := make([]byte, len(serializedBytes))
copy(beforeBytes, serializedBytes)

r := bytes.NewReader(serializedBytes)
afterUndo := new(UndoBlock)
afterUndo.Deserialize(r)

afterSize := afterUndo.SerializeSize()

if beforeSize != afterSize {
str := fmt.Errorf("Serialized sizes differ. Before:%dAfter:%d", beforeSize, afterSize)
t.Error(str)
}

afterBuf := make([]byte, 0, afterSize)
afterW := bytes.NewBuffer(afterBuf)
err = afterUndo.Serialize(afterW)
if err != nil {
t.Fatal(err)
}
afterBytes := afterW.Bytes()

if !bytes.Equal(beforeBytes[:], afterBytes[:]) {
str := fmt.Errorf("Serialized bytes differ. Before:%x after:%x", beforeBytes, afterBytes)
t.Error(str)
}
}
}

func TestUndoFixed(t *testing.T) {
rand.Seed(2)

Expand Down

0 comments on commit dca867c

Please sign in to comment.