tutus-chain/pkg/core/native/event_archival.go

241 lines
6.9 KiB
Go
Executable File

package native
import (
"encoding/binary"
"github.com/tutus-one/tutus-chain/pkg/core/dao"
"github.com/tutus-one/tutus-chain/pkg/core/storage"
"github.com/tutus-one/tutus-chain/pkg/crypto/hash"
"github.com/tutus-one/tutus-chain/pkg/util"
)
// LOW-001: Event archival system for managing event log growth.
type EventArchivalConfig struct {
RetentionBlocks uint32
ArchiveEnabled bool
CommitmentInterval uint32
}
func DefaultEventArchivalConfig() *EventArchivalConfig {
return &EventArchivalConfig{
RetentionBlocks: 2592000,
ArchiveEnabled: false,
CommitmentInterval: 8640,
}
}
type EventArchivalState struct {
LastArchivedBlock uint32
LastCommitmentBlock uint32
TotalEventsArchived uint64
TotalCommitments uint64
}
type EventCommitment struct {
CommitmentID uint64
StartBlock uint32
EndBlock uint32
MerkleRoot util.Uint256
EventCount uint64
CreatedAt uint32
}
const (
eventArchivalPrefixConfig byte = 0xE0
eventArchivalPrefixState byte = 0xE1
eventArchivalPrefixCommitment byte = 0xE2
eventArchivalPrefixByBlock byte = 0xE3
)
type EventArchiver struct {
contractID int32
}
func NewEventArchiver(contractID int32) *EventArchiver {
return &EventArchiver{contractID: contractID}
}
func (ea *EventArchiver) GetConfig(d *dao.Simple) *EventArchivalConfig {
key := []byte{eventArchivalPrefixConfig}
si := d.GetStorageItem(ea.contractID, key)
if si == nil {
return DefaultEventArchivalConfig()
}
cfg := &EventArchivalConfig{}
cfg.RetentionBlocks = binary.BigEndian.Uint32(si[:4])
cfg.ArchiveEnabled = si[4] == 1
cfg.CommitmentInterval = binary.BigEndian.Uint32(si[5:9])
return cfg
}
func (ea *EventArchiver) PutConfig(d *dao.Simple, cfg *EventArchivalConfig) {
key := []byte{eventArchivalPrefixConfig}
data := make([]byte, 9)
binary.BigEndian.PutUint32(data[:4], cfg.RetentionBlocks)
if cfg.ArchiveEnabled {
data[4] = 1
}
binary.BigEndian.PutUint32(data[5:9], cfg.CommitmentInterval)
d.PutStorageItem(ea.contractID, key, data)
}
func (ea *EventArchiver) GetState(d *dao.Simple) *EventArchivalState {
key := []byte{eventArchivalPrefixState}
si := d.GetStorageItem(ea.contractID, key)
if si == nil {
return &EventArchivalState{}
}
st := &EventArchivalState{}
st.LastArchivedBlock = binary.BigEndian.Uint32(si[:4])
st.LastCommitmentBlock = binary.BigEndian.Uint32(si[4:8])
st.TotalEventsArchived = binary.BigEndian.Uint64(si[8:16])
st.TotalCommitments = binary.BigEndian.Uint64(si[16:24])
return st
}
func (ea *EventArchiver) PutState(d *dao.Simple, st *EventArchivalState) {
key := []byte{eventArchivalPrefixState}
data := make([]byte, 24)
binary.BigEndian.PutUint32(data[:4], st.LastArchivedBlock)
binary.BigEndian.PutUint32(data[4:8], st.LastCommitmentBlock)
binary.BigEndian.PutUint64(data[8:16], st.TotalEventsArchived)
binary.BigEndian.PutUint64(data[16:24], st.TotalCommitments)
d.PutStorageItem(ea.contractID, key, data)
}
func (ea *EventArchiver) CreateCommitment(d *dao.Simple, startBlock, endBlock uint32, events [][]byte) *EventCommitment {
st := ea.GetState(d)
merkleRoot := computeEventsMerkleRoot(events)
commitment := &EventCommitment{
CommitmentID: st.TotalCommitments + 1,
StartBlock: startBlock,
EndBlock: endBlock,
MerkleRoot: merkleRoot,
EventCount: uint64(len(events)),
CreatedAt: endBlock,
}
ea.putCommitment(d, commitment)
st.LastCommitmentBlock = endBlock
st.TotalCommitments++
st.TotalEventsArchived += uint64(len(events))
ea.PutState(d, st)
return commitment
}
func (ea *EventArchiver) GetCommitment(d *dao.Simple, commitmentID uint64) *EventCommitment {
key := make([]byte, 9)
key[0] = eventArchivalPrefixCommitment
binary.BigEndian.PutUint64(key[1:], commitmentID)
si := d.GetStorageItem(ea.contractID, key)
if si == nil {
return nil
}
c := &EventCommitment{}
c.CommitmentID = binary.BigEndian.Uint64(si[:8])
c.StartBlock = binary.BigEndian.Uint32(si[8:12])
c.EndBlock = binary.BigEndian.Uint32(si[12:16])
copy(c.MerkleRoot[:], si[16:48])
c.EventCount = binary.BigEndian.Uint64(si[48:56])
c.CreatedAt = binary.BigEndian.Uint32(si[56:60])
return c
}
func (ea *EventArchiver) putCommitment(d *dao.Simple, c *EventCommitment) {
key := make([]byte, 9)
key[0] = eventArchivalPrefixCommitment
binary.BigEndian.PutUint64(key[1:], c.CommitmentID)
data := make([]byte, 60)
binary.BigEndian.PutUint64(data[:8], c.CommitmentID)
binary.BigEndian.PutUint32(data[8:12], c.StartBlock)
binary.BigEndian.PutUint32(data[12:16], c.EndBlock)
copy(data[16:48], c.MerkleRoot[:])
binary.BigEndian.PutUint64(data[48:56], c.EventCount)
binary.BigEndian.PutUint32(data[56:60], c.CreatedAt)
d.PutStorageItem(ea.contractID, key, data)
blockKey := make([]byte, 5)
blockKey[0] = eventArchivalPrefixByBlock
binary.BigEndian.PutUint32(blockKey[1:], c.EndBlock)
binary.BigEndian.PutUint64(data[:8], c.CommitmentID)
d.PutStorageItem(ea.contractID, blockKey, data[:8])
}
func (ea *EventArchiver) GetCommitmentForBlock(d *dao.Simple, blockHeight uint32) *EventCommitment {
var foundID uint64
prefix := []byte{eventArchivalPrefixByBlock}
d.Seek(ea.contractID, storage.SeekRange{Prefix: prefix}, func(k, v []byte) bool {
if len(k) >= 4 && len(v) >= 8 {
endBlock := binary.BigEndian.Uint32(k[:4])
if endBlock >= blockHeight {
foundID = binary.BigEndian.Uint64(v[:8])
return false
}
}
return true
})
if foundID == 0 {
return nil
}
return ea.GetCommitment(d, foundID)
}
func (ea *EventArchiver) ShouldCreateCommitment(d *dao.Simple, currentBlock uint32) bool {
cfg := ea.GetConfig(d)
if !cfg.ArchiveEnabled {
return false
}
st := ea.GetState(d)
return currentBlock >= st.LastCommitmentBlock+cfg.CommitmentInterval
}
func (ea *EventArchiver) CanPruneEvents(d *dao.Simple, blockHeight, currentBlock uint32) bool {
cfg := ea.GetConfig(d)
if !cfg.ArchiveEnabled {
return false
}
if currentBlock < blockHeight+cfg.RetentionBlocks {
return false
}
commitment := ea.GetCommitmentForBlock(d, blockHeight)
return commitment != nil
}
func computeEventsMerkleRoot(events [][]byte) util.Uint256 {
if len(events) == 0 {
return util.Uint256{}
}
hashes := make([]util.Uint256, len(events))
for i, event := range events {
hashes[i] = hash.Sha256(event)
}
for len(hashes) > 1 {
if len(hashes)%2 == 1 {
hashes = append(hashes, hashes[len(hashes)-1])
}
newHashes := make([]util.Uint256, len(hashes)/2)
for i := 0; i < len(hashes); i += 2 {
combined := make([]byte, 64)
copy(combined[:32], hashes[i][:])
copy(combined[32:], hashes[i+1][:])
newHashes[i/2] = hash.Sha256(combined)
}
hashes = newHashes
}
return hashes[0]
}
func VerifyEventInCommitment(eventData []byte, merkleProof []util.Uint256, index int, merkleRoot util.Uint256) bool {
h := hash.Sha256(eventData)
for _, proofElement := range merkleProof {
var combined []byte
if index%2 == 0 {
combined = append(h[:], proofElement[:]...)
} else {
combined = append(proofElement[:], h[:]...)
}
h = hash.Sha256(combined)
index /= 2
}
return h == merkleRoot
}