Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.
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
24 changes: 17 additions & 7 deletions chain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,31 @@ func (b *StatelessBlock) ID() ids.ID { return b.id }
func (b *StatelessBlock) verify() (*StatelessBlock, *versiondb.Database, error) {
g := b.vm.Genesis()

// Perform basic correctness checks before doing any expensive work
if len(b.Txs) == 0 {
return nil, nil, ErrNoTxs
}
if b.Timestamp().Unix() >= time.Now().Add(futureBound).Unix() {
return nil, nil, ErrTimestampTooLate
}
blockSize := uint64(0)
for _, tx := range b.Txs {
blockSize += tx.LoadUnits(g)
if blockSize > g.MaxBlockSize {
return nil, nil, ErrBlockTooBig
}
}

// Verify parent is available
parent, err := b.vm.GetStatelessBlock(b.Prnt)
if err != nil {
log.Debug("could not get parent", "id", b.Prnt)
return nil, nil, err
}

if len(b.Txs) == 0 {
return nil, nil, ErrNoTxs
}
if b.Timestamp().Unix() < parent.Timestamp().Unix() {
return nil, nil, ErrTimestampTooEarly
}
if b.Timestamp().Unix() >= time.Now().Add(futureBound).Unix() {
return nil, nil, ErrTimestampTooLate
}

context, err := b.vm.ExecutionContext(b.Tmstmp, parent)
if err != nil {
return nil, nil, err
Expand Down
3 changes: 1 addition & 2 deletions chain/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,10 @@ func createTestBlk(
vm := NewMockVM(ctrl)
vm.EXPECT().Genesis().Return(DefaultGenesis()).AnyTimes()
parentBlk.vm = vm

if err := parentBlk.init(); err != nil {
t.Fatal(err)
}
vm.EXPECT().GetStatelessBlock(parentBlk.ID()).Return(parentBlk, nil)
vm.EXPECT().GetStatelessBlock(parentBlk.ID()).Return(parentBlk, nil).AnyTimes()

blk := NewBlock(vm, parentBlk, blkTmpstp, blkCtx)
if uint64(blk.StatefulBlock.Tmstmp) != uint64(blkTmpstp) {
Expand Down
21 changes: 18 additions & 3 deletions chain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,28 @@ func BuildBlock(vm VM, preferred ids.ID) (snowman.Block, error) {
b.Winners = map[ids.ID]*Activity{}
b.Txs = []*Transaction{}
units := uint64(0)
for units < g.TargetUnits && mempool.Len() > 0 {

// Restorable txs after block attempt finishes
unusableTxs := []*Transaction{}
defer func() {
for _, tx := range unusableTxs {
mempool.Add(tx)
}
}()

for mempool.Len() > 0 {
next, price := mempool.PopMax()
if price < b.Price {
mempool.Add(next)
log.Debug("skipping tx: too low price", "block price", b.Price, "tx price", next.GetPrice())
log.Debug("skipping tx: too low price", "block price", b.Price, "tx price", price)
break
}
nextLoad := next.LoadUnits(g)
if units+nextLoad > g.MaxBlockSize {
unusableTxs = append(unusableTxs, next)
log.Debug("skipping tx: too large", "block size", units, "tx load", nextLoad)
continue // could be txs that fit that are smaller
}
// Verify that changes pass
tvdb := versiondb.New(vdb)
if err := next.Execute(g, tvdb, b, context); err != nil {
Expand All @@ -66,7 +81,7 @@ func BuildBlock(vm VM, preferred ids.ID) (snowman.Block, error) {
}
// Wait to add prefix until after verification
b.Txs = append(b.Txs, next)
units += next.LoadUnits(g)
units += nextLoad
}
vdb.Abort()

Expand Down
20 changes: 9 additions & 11 deletions chain/claim_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,27 @@ func (c *ClaimTx) Execute(t *TransactionContext) error {
return nil
}

// [spaceUnits] requires the caller to pay more to get spaces of
// [spaceNameUnits] requires the caller to pay more to get spaces of
// a shorter length because they are more desirable. This creates a "lottery"
// mechanism where the people that spend the most mining power will win the
// space.
//
// [spaceUnits] should only be called on a space that is valid
func spaceUnits(g *Genesis, s string) uint64 {
// [spaceNameUnits] should only be called on a space that is valid
func spaceNameUnits(g *Genesis, s string) uint64 {
desirability := uint64(parser.MaxIdentifierSize - len(s))
if uint64(len(s)) > g.ClaimTier2Size {
return desirability * g.ClaimTier3Multiplier
desirability *= g.SpaceDesirabilityMultiplier
if desirability < g.MinClaimFee {
return g.MinClaimFee
}
if uint64(len(s)) > g.ClaimTier1Size {
return desirability * g.ClaimTier2Multiplier
}
return desirability * g.ClaimTier1Multiplier
return desirability
}

func (c *ClaimTx) FeeUnits(g *Genesis) uint64 {
return c.LoadUnits(g) + spaceUnits(g, c.Space)
return c.LoadUnits(g) + spaceNameUnits(g, c.Space)
}

func (c *ClaimTx) LoadUnits(g *Genesis) uint64 {
return c.BaseTx.LoadUnits(g) * g.ClaimFeeMultiplier
return c.BaseTx.LoadUnits(g) * g.ClaimLoadMultiplier
}

func (c *ClaimTx) Copy() UnsignedTransaction {
Expand Down
4 changes: 2 additions & 2 deletions chain/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const (
// codecVersion is the current default codec version
codecVersion = 0

// maxSize is 2MB to support large values
maxSize = 2 * units.MiB
// maxSize is 4MB to support large values
maxSize = 4 * units.MiB
)

var codecManager codec.Manager
Expand Down
6 changes: 5 additions & 1 deletion chain/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
)

var (
// Genesis Correctness
ErrInvalidMagic = errors.New("invalid magic")
ErrInvalidBlockRate = errors.New("invalid block rate")

// Block Correctness
ErrTimestampTooEarly = errors.New("block timestamp too early")
ErrTimestampTooLate = errors.New("block timestamp too late")
Expand All @@ -18,7 +22,6 @@ var (
ErrParentBlockNotVerified = errors.New("parent block not verified or accepted")

// Tx Correctness
ErrInvalidMagic = errors.New("invalid magic")
ErrInvalidBlockID = errors.New("invalid blockID")
ErrInvalidSignature = errors.New("invalid signature")
ErrDuplicateTx = errors.New("duplicate transaction")
Expand All @@ -38,4 +41,5 @@ var (
ErrUnauthorized = errors.New("sender is not authorized")
ErrInvalidBalance = errors.New("invalid balance")
ErrNonActionable = errors.New("transaction doesn't do anything")
ErrBlockTooBig = errors.New("block too big")
)
81 changes: 44 additions & 37 deletions chain/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ import (
log "github.com/inconshreveable/log15"
)

const (
LotteryRewardDivisor = 100
MinBlockCost = 0

DefaultFreeClaimStorage = 1 * units.MiB
DefaultValueUnitSize = 1 * units.KiB
DefaultFreeClaimUnits = DefaultFreeClaimStorage / DefaultValueUnitSize
DefaultFreeClaimDuration = 60 * 60 * 24 * 30 // 30 Days

DefaultLookbackWindow = 60
)

type Airdrop struct {
// Address strings are hex-formatted common.Address
Address common.Address `serialize:"true" json:"address"`
Expand All @@ -38,30 +50,26 @@ type Genesis struct {
MaxValueSize uint64 `serialize:"true" json:"maxValueSize"`

// Claim Params
ClaimFeeMultiplier uint64 `serialize:"true" json:"claimFeeMultiplier"`
ClaimTier3Multiplier uint64 `serialize:"true" json:"claimTier3Multiplier"`
ClaimTier2Size uint64 `serialize:"true" json:"claimTier2Size"`
ClaimTier2Multiplier uint64 `serialize:"true" json:"claimTier2Multiplier"`
ClaimTier1Size uint64 `serialize:"true" json:"claimTier1Size"`
ClaimTier1Multiplier uint64 `serialize:"true" json:"claimTier1Multiplier"`
ClaimLoadMultiplier uint64 `serialize:"true" json:"claimLoadMultiplier"`
MinClaimFee uint64 `serialize:"true" json:"minClaimFee"`
SpaceDesirabilityMultiplier uint64 `serialize:"true" json:"spaceDesirabilityMultiplier"`

// Lifeline Params
PrefixRenewalDiscount uint64 `serialize:"true" json:"prefixRenewalDiscount"`
SpaceRenewalDiscount uint64 `serialize:"true" json:"prefixRenewalDiscount"`

// Reward Params
ClaimReward uint64 `serialize:"true" json:"claimReward"`
LifelineUnitReward uint64 `serialize:"true" json:"lifelineUnitReward"`
ClaimReward uint64 `serialize:"true" json:"claimReward"`

// Mining Reward (% of min required fee)
LotteryRewardMultipler uint64 `serialize:"true" json:"lotteryRewardMultipler"`
LotteryRewardDivisor uint64 `serialize:"true" json:"lotteryRewardDivisor"`
LotteryRewardMultipler uint64 `serialize:"true" json:"lotteryRewardMultipler"` // divided by 100

// Fee Mechanism Params
LookbackWindow int64 `serialize:"true" json:"lookbackWindow"`
BlockTarget int64 `serialize:"true" json:"blockTarget"`
TargetUnits uint64 `serialize:"true" json:"targetUnits"`
MinPrice uint64 `serialize:"true" json:"minPrice"`
MinBlockCost uint64 `serialize:"true" json:"minBlockCost"`
MinPrice uint64 `serialize:"true" json:"minPrice"`
LookbackWindow int64 `serialize:"true" json:"lookbackWindow"`
TargetBlockRate int64 `serialize:"true" json:"targetBlockRate"` // seconds
TargetBlockSize uint64 `serialize:"true" json:"targetBlockSize"` // units
MaxBlockSize uint64 `serialize:"true" json:"maxBlockSize"` // units
BlockCostEnabled bool `serialize:"true" json:"blockCostEnabled"`

// Allocations
CustomAllocation []*CustomAllocation `serialize:"true" json:"customAllocation"`
Expand All @@ -72,51 +80,50 @@ type Genesis struct {
func DefaultGenesis() *Genesis {
return &Genesis{
// Tx params
BaseTxUnits: 10,
BaseTxUnits: 1,

// SetTx params
ValueUnitSize: 256, // 256B
MaxValueSize: 64 * units.KiB, // (250 Units)
ValueUnitSize: DefaultValueUnitSize,
MaxValueSize: 256 * units.KiB,

// Claim Params
ClaimFeeMultiplier: 5,
ClaimTier3Multiplier: 1,
ClaimTier2Size: 36,
ClaimTier2Multiplier: 5,
ClaimTier1Size: 12,
ClaimTier1Multiplier: 25,
ClaimLoadMultiplier: 5,
MinClaimFee: 100,
SpaceDesirabilityMultiplier: 5,

// Lifeline Params
PrefixRenewalDiscount: 5,
SpaceRenewalDiscount: 10,

// Reward Params
ClaimReward: 60 * 60 * 24 * 15, // 15 Days
LifelineUnitReward: 60 * 60, // 1 Hours Per Fee Unit
ClaimReward: DefaultFreeClaimUnits * DefaultFreeClaimDuration,

// Lottery Reward (80% of tx.FeeUnits() * block.Price)
LotteryRewardMultipler: 8,
LotteryRewardDivisor: 10,
// Lottery Reward (50% of tx.FeeUnits() * block.Price)
LotteryRewardMultipler: 50,

// Fee Mechanism Params
LookbackWindow: 60, // 60 Seconds
BlockTarget: 1, // 1 Block per Second
TargetUnits: 10 * 512 * 60, // 5012 Units Per Block (~1.2MB of SetTx)
MinPrice: 1, // (50 for easiest claim)
MinBlockCost: 0, // Minimum Unit Overhead
LookbackWindow: DefaultLookbackWindow, // 60 Seconds
TargetBlockRate: 1, // 1 Block per Second
TargetBlockSize: 1500, // 1500 Units Per Block (~1.5MB of SetTx)
MaxBlockSize: 2000, // 2000 Units (~2MB)
MinPrice: 1,
BlockCostEnabled: true,
}
}

func (g *Genesis) StatefulBlock() *StatefulBlock {
return &StatefulBlock{
Price: g.MinPrice,
Cost: g.MinBlockCost,
Cost: MinBlockCost,
}
}

func (g *Genesis) Verify() error {
if g.Magic == 0 {
return ErrInvalidMagic
}
if g.TargetBlockRate == 0 {
return ErrInvalidBlockRate
}
return nil
}

Expand Down
14 changes: 11 additions & 3 deletions chain/lifeline_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type LifelineTx struct {
}

func (l *LifelineTx) Execute(t *TransactionContext) error {
if l.Units == 0 {
return ErrNonActionable
}

if err := parser.CheckContents(l.Space); err != nil {
return err
}
Expand All @@ -43,20 +47,24 @@ func (l *LifelineTx) Execute(t *TransactionContext) error {
}
// Lifeline spread across all units
lastExpiry := i.Expiry
i.Expiry += g.LifelineUnitReward * l.Units / i.Units
i.Expiry += (g.ClaimReward * l.Units) / i.Units
return PutSpaceInfo(t.Database, []byte(l.Space), i, lastExpiry)
}

func (l *LifelineTx) FeeUnits(g *Genesis) uint64 {
// FeeUnits are discounted so that, all else equal, it is easier for an owner
// to retain their space than for another to claim it.
discountedPrefixUnits := spaceUnits(g, l.Space) / g.PrefixRenewalDiscount
dSpaceNameUnits := spaceNameUnits(g, l.Space) / g.SpaceRenewalDiscount

// The more desirable the space, the more it costs to maintain it.
//
// Note, this heavy base cost incentivizes users to send fewer transactions
// to extend their space's life instead of many small ones.
return l.LoadUnits(g) + discountedPrefixUnits + l.Units
return l.LoadUnits(g) + dSpaceNameUnits*l.Units
}

func (l *LifelineTx) LoadUnits(g *Genesis) uint64 {
return l.BaseTx.LoadUnits(g) * g.ClaimLoadMultiplier
}

func (l *LifelineTx) Copy() UnsignedTransaction {
Expand Down
10 changes: 8 additions & 2 deletions chain/lifeline_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestLifelineTx(t *testing.T) {
err error
}{
{ // invalid when prefix info is missing
utx: &LifelineTx{BaseTx: &BaseTx{}, Space: "foo"},
utx: &LifelineTx{BaseTx: &BaseTx{}, Space: "foo", Units: 1},
blockTime: 1,
sender: sender,
err: ErrSpaceMissing,
Expand All @@ -44,10 +44,16 @@ func TestLifelineTx(t *testing.T) {
sender: sender,
err: nil,
},
{ // successful lifeline when prefix info is not missing
{ // invalid when units is missing
utx: &LifelineTx{BaseTx: &BaseTx{}, Space: "foo"},
blockTime: 1,
sender: sender,
err: ErrNonActionable,
},
{ // successful lifeline when prefix info and units is not missing
utx: &LifelineTx{BaseTx: &BaseTx{}, Space: "foo", Units: 1},
blockTime: 1,
sender: sender,
err: nil,
},
{ // successful lifeline non-zero units
Expand Down
3 changes: 2 additions & 1 deletion chain/mempool_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion chain/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (t *Transaction) Execute(g *Genesis, db database.Database, blk *StatelessBl
// Do not process any rewards if it is just a dummy block
return nil
}
rewardAmount := t.FeeUnits(g) * blk.Price * g.LotteryRewardMultipler / g.LotteryRewardDivisor
rewardAmount := t.FeeUnits(g) * blk.Price * g.LotteryRewardMultipler / LotteryRewardDivisor
recipient, distributed, err := ApplyReward(db, blk.ID(), t.ID(), t.sender, rewardAmount)
if err != nil {
return err
Expand Down
Loading