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
Show all changes
29 commits
Select commit Hold shift + click to select a range
1baade4
add load vs fee units
patrick-ogrady Jan 9, 2022
b3926ad
miner refactor
patrick-ogrady Jan 9, 2022
e248573
fix integration test
patrick-ogrady Jan 9, 2022
07eda2a
fix mempool test
patrick-ogrady Jan 9, 2022
b8f0ad7
chain test passing
patrick-ogrady Jan 10, 2022
7cee0d8
add beneficiary
patrick-ogrady Jan 10, 2022
ad807c1
add TODO
patrick-ogrady Jan 10, 2022
8bc438d
nits
patrick-ogrady Jan 10, 2022
6d7dfb4
fix client race
patrick-ogrady Jan 10, 2022
2113cf1
integration test passing
patrick-ogrady Jan 10, 2022
244bd7c
update mocks
patrick-ogrady Jan 10, 2022
f938b9c
fix e2e
patrick-ogrady Jan 10, 2022
11864cd
cleanup services
patrick-ogrady Jan 10, 2022
80f2456
reduce need for casting
patrick-ogrady Jan 10, 2022
80326fa
nits
patrick-ogrady Jan 10, 2022
5b21040
tweak difficulty
patrick-ogrady Jan 10, 2022
3742fba
fix miner bug
patrick-ogrady Jan 10, 2022
473e2cd
fix work estimate
patrick-ogrady Jan 10, 2022
2649c5c
fix integration tests
patrick-ogrady Jan 10, 2022
b758e04
fix e2e
patrick-ogrady Jan 10, 2022
2ea3f0d
fix mempool test
patrick-ogrady Jan 10, 2022
fbffe7a
make mining loop faster
patrick-ogrady Jan 10, 2022
14403ac
use ptr
patrick-ogrady Jan 10, 2022
3378689
add multi-threaded miner
patrick-ogrady Jan 10, 2022
af699b0
linting
patrick-ogrady Jan 10, 2022
2c58add
extend timeout
patrick-ogrady Jan 10, 2022
35976fd
update mocks
patrick-ogrady Jan 10, 2022
b7dff27
fix linter
patrick-ogrady Jan 10, 2022
859f607
add comment about expiry during bootstrapping
patrick-ogrady Jan 10, 2022
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ TODO: Extend on
* Prefixes (address prefixes reserved)
* Hashed Value Keys
* Prefix Expiry (based on weight of all key-values)
* Load Units vs Fee Units
* Lifeline Rewards (why run a node -> don't need to mine)

# RPC
## /public
* range query

## /private
* set beneficiary

# Quick start

Expand Down
21 changes: 20 additions & 1 deletion chain/base_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,25 @@ func (b *BaseTx) ExecuteBase() error {
return nil
}

func (b *BaseTx) Units() uint64 {
func (b *BaseTx) FeeUnits() uint64 {
return BaseTxUnits
}

func (b *BaseTx) LoadUnits() uint64 {
return b.FeeUnits()
}

func (b *BaseTx) Copy() *BaseTx {
sender := [crypto.SECP256K1RPKLen]byte{}
copy(sender[:], b.Sender[:])
blockID := ids.ID{}
copy(blockID[:], b.BlockID[:])
prefix := make([]byte, len(b.Prefix))
copy(prefix, b.Prefix)
return &BaseTx{
Sender: sender,
BlockID: blockID,
Graffiti: b.Graffiti,
Prefix: prefix,
}
}
40 changes: 22 additions & 18 deletions chain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ const futureBound = 10 * time.Second
var _ snowman.Block = &StatelessBlock{}

type StatefulBlock struct {
Prnt ids.ID `serialize:"true" json:"parent"`
Tmstmp int64 `serialize:"true" json:"timestamp"`
Hght uint64 `serialize:"true" json:"height"`
Difficulty uint64 `serialize:"true" json:"difficulty"` // difficulty per unit
Cost uint64 `serialize:"true" json:"cost"`
Txs []*Transaction `serialize:"true" json:"txs"`
Prnt ids.ID `serialize:"true" json:"parent"`
Tmstmp int64 `serialize:"true" json:"timestamp"`
Hght uint64 `serialize:"true" json:"height"`
Difficulty uint64 `serialize:"true" json:"difficulty"` // difficulty per unit
Cost uint64 `serialize:"true" json:"cost"`
Txs []*Transaction `serialize:"true" json:"txs"`
Beneficiary []byte `serialize:"true" json:"beneficiary"` // prefix to reward
}

// Stateless is defined separately from "Block"
Expand All @@ -45,14 +46,15 @@ type StatelessBlock struct {
onAcceptDB *versiondb.Database
}

func NewBlock(vm VM, parent snowman.Block, tmstp int64, context *Context) *StatelessBlock {
func NewBlock(vm VM, parent snowman.Block, tmstp int64, beneficiary []byte, context *Context) *StatelessBlock {
return &StatelessBlock{
StatefulBlock: &StatefulBlock{
Tmstmp: tmstp,
Prnt: parent.ID(),
Hght: parent.Height() + 1,
Difficulty: context.NextDifficulty,
Cost: context.NextCost,
Tmstmp: tmstp,
Prnt: parent.ID(),
Hght: parent.Height() + 1,
Difficulty: context.NextDifficulty,
Cost: context.NextCost,
Beneficiary: beneficiary,
},
vm: vm,
st: choices.Processing,
Expand Down Expand Up @@ -117,15 +119,11 @@ func (b *StatelessBlock) ID() ids.ID { return b.id }
// verify checks the correctness of a block and then returns the
// *versiondb.Database computed during execution.
func (b *StatelessBlock) verify() (*StatelessBlock, *versiondb.Database, error) {
prnt, err := b.vm.GetBlock(b.Prnt)
parent, err := b.vm.GetStatelessBlock(b.Prnt)
if err != nil {
log.Debug("could not get parent", "id", b.Prnt)
return nil, nil, err
}
parent, ok := prnt.(*StatelessBlock)
if !ok {
return nil, nil, fmt.Errorf("unexpected snowman.Block %T, expected *StatelessBlock", prnt)
}

if len(b.Txs) == 0 {
return nil, nil, ErrNoTxs
Expand All @@ -146,6 +144,7 @@ func (b *StatelessBlock) verify() (*StatelessBlock, *versiondb.Database, error)
if b.Difficulty != context.NextDifficulty {
return nil, nil, ErrInvalidDifficulty
}

parentState, err := parent.onAccept()
if err != nil {
return nil, nil, err
Expand All @@ -156,6 +155,10 @@ func (b *StatelessBlock) verify() (*StatelessBlock, *versiondb.Database, error)
if err := ExpireNext(onAcceptDB, parent.Tmstmp, b.Tmstmp); err != nil {
return nil, nil, err
}
// Reward producer (if [b.Beneficiary] is non-nil)
if err := Reward(onAcceptDB, b.Beneficiary); err != nil {
return nil, nil, err
}

// Process new transactions
log.Debug("build context", "height", b.Hght, "difficulty", b.Difficulty, "cost", b.Cost)
Expand All @@ -164,7 +167,7 @@ func (b *StatelessBlock) verify() (*StatelessBlock, *versiondb.Database, error)
if err := tx.Execute(onAcceptDB, b.Tmstmp, context); err != nil {
return nil, nil, err
}
surplusWork += (tx.Difficulty() - b.Difficulty) * tx.Units()
surplusWork += (tx.Difficulty() - b.Difficulty) * tx.FeeUnits()
}
// Ensure enough work is performed to compensate for block production speed
requiredSurplus := b.Difficulty * b.Cost
Expand All @@ -178,6 +181,7 @@ func (b *StatelessBlock) verify() (*StatelessBlock, *versiondb.Database, error)
func (b *StatelessBlock) Verify() error {
parent, onAcceptDB, err := b.verify()
if err != nil {
log.Debug("block verification failed", "blkID", b.ID(), "error", err)
return err
}
b.onAcceptDB = onAcceptDB
Expand Down
4 changes: 2 additions & 2 deletions chain/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ func createTestBlk(
if err := parentBlk.init(); err != nil {
t.Fatal(err)
}
vm.EXPECT().GetBlock(parentBlk.ID()).Return(parentBlk, nil)
vm.EXPECT().GetStatelessBlock(parentBlk.ID()).Return(parentBlk, nil)

blk := NewBlock(vm, parentBlk, blkTmpstp, blkCtx)
blk := NewBlock(vm, parentBlk, blkTmpstp, nil, blkCtx)
if uint64(blk.StatefulBlock.Tmstmp) != uint64(blkTmpstp) {
t.Fatalf("blk.StatefulBlock.Tmstmp expected %d, got %d", blkTmpstp, blk.StatefulBlock.Tmstmp)
}
Expand Down
29 changes: 18 additions & 11 deletions chain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package chain

import (
"fmt"
"time"

"github.com/ava-labs/avalanchego/database/versiondb"
Expand All @@ -17,31 +16,38 @@ func BuildBlock(vm VM, preferred ids.ID) (snowman.Block, error) {
log.Debug("attempting block building")

nextTime := time.Now().Unix()
prnt, err := vm.GetBlock(preferred)
parent, err := vm.GetStatelessBlock(preferred)
if err != nil {
log.Debug("block building failed: couldn't get parent", "err", err)
return nil, err
}
parent, ok := prnt.(*StatelessBlock)
if !ok {
return nil, fmt.Errorf("unexpected snowman.Block %T, expected *StatelessBlock", prnt)
}
context, err := vm.ExecutionContext(nextTime, parent)
if err != nil {
log.Debug("block building failed: couldn't get execution context", "err", err)
return nil, err
}
b := NewBlock(vm, parent, nextTime, context)
b := NewBlock(vm, parent, nextTime, vm.Beneficiary(), context)

// Clean out invalid txs
mempool := vm.Mempool()
mempool.Prune(context.RecentBlockIDs)

// Select new transactions
parentDB, err := parent.onAccept()
if err != nil {
log.Debug("block building failed: couldn't get parent db", "err", err)
return nil, err
}
mempool := vm.Mempool()
mempool.Prune(context.RecentBlockIDs) // clean out invalid txs
vdb := versiondb.New(parentDB)

// Remove all expired prefixes
if err := ExpireNext(vdb, parent.Tmstmp, b.Tmstmp); err != nil {
return nil, err
}
// Reward producer (if [b.Beneficiary] is non-nil)
if err := Reward(vdb, b.Beneficiary); err != nil {
return nil, err
}

b.Txs = []*Transaction{}
units := uint64(0)
for units < TargetUnits && mempool.Len() > 0 {
Expand All @@ -62,8 +68,9 @@ 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.Units()
units += next.LoadUnits()
}
vdb.Abort()

// Compute block hash and marshaled representation
if err := b.init(); err != nil {
Expand Down
33 changes: 24 additions & 9 deletions chain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,36 @@ package chain

import (
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/units"
)

// TODO: load from genesis
const (
ExpiryTime = 60 * 60 * 24 * 30 // 30 Days
ValueUnitSize = 256 // 256B
MaxValueSize = 1 << 10 * 128 // 128KB (500 Units)
BaseTxUnits = 10

// Tx params
BaseTxUnits = 10

// SetTx params
ValueUnitSize = 256 // 256B
MaxValueSize = 128 * units.KiB // (500 Units)

// Claim Params
ClaimFeeMultiplier = 5
ExpiryTime = 60 * 60 * 24 * 30 // 30 Days
ClaimTier3Multiplier = 1
ClaimTier2Size = 36
ClaimTier2Multiplier = 5
ClaimTier1Size = 12
ClaimTier1Multiplier = 25

// Lifeline Params
PrefixRenewalDiscount = 5

// Fee Mechanism Params
LookbackWindow = 60 // 60 Seconds
BlockTarget = 1 // 1 Block per Second
TargetUnits = BaseTxUnits * 512 * LookbackWindow / BlockTarget // 512 Units Per Block

MinDifficulty = 10 // ~10ms per unit (100 ms for claim)
MinBlockCost = 1 // Minimum Unit Overhead
TargetUnits = BaseTxUnits * 512 * LookbackWindow / BlockTarget // 5012 Units Per Block (~1.2MB of SetTx)
MinDifficulty = 100 // ~100ms per unit (~5s for easiest claim)
MinBlockCost = 1 // Minimum Unit Overhead
)

type Context struct {
Expand Down
32 changes: 32 additions & 0 deletions chain/claim_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/utils/crypto"
"github.com/ava-labs/quarkvm/parser"
)

var _ UnsignedTransaction = &ClaimTx{}
Expand Down Expand Up @@ -45,3 +46,34 @@ func (c *ClaimTx) Execute(db database.Database, blockTime uint64) error {
}
return nil
}

// [prefixUnits] requires the caller to produce more work to get prefixes 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
// prefix.
//
// [prefixUnits] should only be called on a prefix that is valid
func prefixUnits(p []byte) uint64 {
desirability := parser.MaxKeySize - len(p)
if len(p) > ClaimTier2Size {
return uint64(desirability * ClaimTier3Multiplier)
}
if len(p) > ClaimTier1Size {
return uint64(desirability * ClaimTier2Multiplier)
}
return uint64(desirability * ClaimTier1Multiplier)
}

func (c *ClaimTx) FeeUnits() uint64 {
return c.LoadUnits() + prefixUnits(c.Prefix)
}

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

func (c *ClaimTx) Copy() UnsignedTransaction {
return &ClaimTx{
BaseTx: c.BaseTx.Copy(),
}
}
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 1MB to support large blocks (~9 large key settings)
maxSize = 1 * units.MiB
// maxSize is 2MB to support large values
maxSize = 2 * units.MiB
)

var codecManager codec.Manager
Expand Down
25 changes: 20 additions & 5 deletions chain/lifeline_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,32 @@ type LifelineTx struct {
*BaseTx `serialize:"true" json:"baseTx"`
}

func (l *LifelineTx) Execute(db database.Database, blockTime uint64) error {
i, has, err := GetPrefixInfo(db, l.Prefix)
func addLife(db database.KeyValueReaderWriter, prefix []byte) error {
i, has, err := GetPrefixInfo(db, prefix)
if err != nil {
return err
}
// Cannot add lifeline to missing prefix
// Cannot add time to missing prefix
if !has {
return ErrPrefixMissing
}
// Lifeline spread across all units
lastExpiry := i.Expiry
i.Expiry += ExpiryTime / i.Units
return PutPrefixInfo(db, l.Prefix, i, lastExpiry)
prefixPenalty := prefixUnits(prefix) / PrefixRenewalDiscount
if prefixPenalty < 1 { // avoid division by 0
prefixPenalty = 1
}

i.Expiry += ExpiryTime / i.Units / prefixPenalty
return PutPrefixInfo(db, prefix, i, lastExpiry)
}

func (l *LifelineTx) Execute(db database.Database, blockTime uint64) error {
return addLife(db, l.Prefix)
}

func (l *LifelineTx) Copy() UnsignedTransaction {
return &LifelineTx{
BaseTx: l.BaseTx.Copy(),
}
}
14 changes: 14 additions & 0 deletions chain/mempool_mock.go

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

21 changes: 21 additions & 0 deletions chain/reward.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (C) 2019-2021, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package chain

import (
"github.com/ava-labs/avalanchego/database"

"github.com/ava-labs/quarkvm/parser"
)

func Reward(db database.KeyValueReaderWriter, prefix []byte) error {
// If there is no one to reward, do nothing
if len(prefix) == 0 {
return nil
}
if err := parser.CheckPrefix(prefix); err != nil {
return err
}
return addLife(db, prefix)
}
Loading