241 lines
6.9 KiB
Go
Executable File
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
|
|
}
|