Skip to content
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,5 @@ osxcross/
target/
Cargo.lock
**/*.rs.bk

*.code-workspace
28 changes: 27 additions & 1 deletion chain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/consts"
"github.com/ava-labs/hypersdk/merkle"
"github.com/ava-labs/hypersdk/state"
"github.com/ava-labs/hypersdk/utils"
"github.com/ava-labs/hypersdk/window"
Expand All @@ -40,7 +41,8 @@ type StatefulBlock struct {
Tmstmp int64 `json:"timestamp"`
Hght uint64 `json:"height"`

Txs []*Transaction `json:"txs"`
Txs []*Transaction `json:"txs"`
TxsRoot ids.ID `json:"txsRoot"`

// StateRoot is the root of the post-execution state
// of [Prnt].
Expand Down Expand Up @@ -290,6 +292,27 @@ func (b *StatelessBlock) initializeBuilt(
b.containsWarp = true
}
}

// transaction hash generation
// [len(b.Txs)] should be equal to [b.results]
merkleItems := make([][]byte, 0, len(b.Txs))
for i := 0; i < len(b.Txs); i++ {
txID := b.Txs[i].ID()
resultOutput := b.results[i].Output
// [txID + resultOutput]
// txID is a fixed length array, hence [append] will always allocate new memory and copy
// so slice with new address will be returned and no reflect on txID, then later
// we consume those bytes
merkleItems = append(merkleItems, append(txID[:], resultOutput...))
}

// consume bytes to avoid extra copying
root, _, err := merkle.GenerateMerkleRoot(ctx, b.vm.Tracer(), merkleItems, true)
if err != nil {
return err
}
b.TxsRoot = root

return nil
}

Expand Down Expand Up @@ -994,6 +1017,8 @@ func (b *StatefulBlock) Marshal() ([]byte, error) {

p.PackID(b.StateRoot)
p.PackUint64(uint64(b.WarpResults))
p.PackID(b.TxsRoot)

bytes := p.Bytes()
if err := p.Err(); err != nil {
return nil, err
Expand Down Expand Up @@ -1029,6 +1054,7 @@ func UnmarshalBlock(raw []byte, parser Parser) (*StatefulBlock, error) {

p.UnpackID(false, &b.StateRoot)
b.WarpResults = set.Bits64(p.UnpackUint64(false))
p.UnpackID(false, &b.TxsRoot)

// Ensure no leftover bytes
if !p.Empty() {
Expand Down
52 changes: 52 additions & 0 deletions merkle/merkle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package merkle

import (
"context"

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/database/memdb"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/trace"
"github.com/ava-labs/avalanchego/utils/units"
"github.com/ava-labs/avalanchego/x/merkledb"
"github.com/ava-labs/hypersdk/utils"
)

// Generate merkle root for a set of items
func GenerateMerkleRoot(ctx context.Context, tracer trace.Tracer, merkleItems [][]byte, consumeBytes bool) (ids.ID, merkledb.MerkleDB, error) {
batchOps := make([]database.BatchOp, 0, len(merkleItems))

for _, item := range merkleItems {
key := utils.ToID(item)
batchOps = append(batchOps, database.BatchOp{
Key: key[:],
Value: item,
})
}

db, err := merkledb.New(ctx, memdb.New(), merkledb.Config{
BranchFactor: merkledb.BranchFactor16,
HistoryLength: 100,
IntermediateNodeCacheSize: units.MiB,
ValueNodeCacheSize: units.MiB,
Tracer: tracer,
})
if err != nil {
return ids.Empty, nil, err
}

view, err := db.NewView(ctx, merkledb.ViewChanges{BatchOps: batchOps, ConsumeBytes: consumeBytes})
if err != nil {
return ids.Empty, nil, err
}
if err := view.CommitToDB(ctx); err != nil {
return ids.Empty, nil, err
}

root, err := db.GetMerkleRoot(ctx)
if err != nil {
return ids.Empty, nil, err
}

return root, db, nil
}