Skip to content

Commit

Permalink
Merge pull request #15 from ava-labs/coreth-0.8.5-rc.2
Browse files Browse the repository at this point in the history
merge coreth-0.8.5-rc.2
  • Loading branch information
patrick-ogrady authored Feb 16, 2022
2 parents f844d8e + c14727a commit 7d22f34
Show file tree
Hide file tree
Showing 40 changed files with 2,667 additions and 431 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ This chain implements the Ethereum Virtual Machine and supports Solidity smart c

The Subnet EVM runs in a separate process from the main AvalancheGo process and communicates with it over a local gRPC connection.

### AvalancheGo Compatibility
```
[v0.1.0] [email protected]
[v0.1.1] [email protected]
```

## API

The Subnet EVM supports the following API namespaces:
Expand Down Expand Up @@ -68,12 +74,12 @@ and creates a `subnet-evm` genesis file.
```bash
# to startup a local cluster (good for development)
cd ${HOME}/go/src/github.com/ava-labs/subnet-evm
./scripts/run.sh 1.7.4
./scripts/run.sh 1.7.5
```

```bash
# inspect cluster endpoints when ready
cat /tmp/avalanchego-v1.7.4/output.yaml
cat /tmp/avalanchego-v1.7.5/output.yaml
<<COMMENT
endpoint: /ext/bc/2VCAhX6vE3UnXC6s1CBPE6jJ4c4cHWMfPgCptuWS59pQ9vbeLM
logsDir: ...
Expand Down
12 changes: 12 additions & 0 deletions consensus/dummy/dynamic_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin
return newRollupWindow, baseFee, nil
}

// EstiamteNextBaseFee attempts to estimate the next base fee based on a block with [parent] being built at
// [timestamp].
// If [timestamp] is less than the timestamp of [parent], then it uses the same timestamp as parent.
// Warning: This function should only be used in estimation and should not be used when calculating the canonical
// base fee for a subsequent block.
func EstimateNextBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) {
if timestamp < parent.Time {
timestamp = parent.Time
}
return CalcBaseFee(config, parent, timestamp)
}

// selectBigWithinBounds returns [value] if it is within the bounds:
// lowerBound <= value <= upperBound or the bound at either end if [value]
// is outside of the defined boundaries.
Expand Down
77 changes: 74 additions & 3 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -1080,15 +1080,15 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
// Ensure the user sees large reorgs
if len(oldChain) > 0 && len(newChain) > 0 {
logFn := log.Info
msg := "Chain reorg detected"
msg := "Resetting chain preference"
if len(oldChain) > 63 {
msg = "Large chain reorg detected"
msg = "Large chain preference change detected"
logFn = log.Warn
}
logFn(msg, "number", commonBlock.Number(), "hash", commonBlock.Hash(),
"drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash())
} else {
log.Warn("Unlikely reorg (rewind to ancestor) occurred", "oldnum", oldHead.Number(), "oldhash", oldHead.Hash(), "newnum", newHead.Number(), "newhash", newHead.Hash())
log.Warn("Unlikely preference change (rewind to ancestor) occurred", "oldnum", oldHead.Number(), "oldhash", oldHead.Hash(), "newnum", newHead.Number(), "newhash", newHead.Hash())
}
// Insert the new chain(except the head block(reverse order)),
// taking care of the proper incremental order.
Expand Down Expand Up @@ -1293,3 +1293,74 @@ func (bc *BlockChain) reprocessState(current *types.Block, reexec uint64) error
}
return nil
}

// CleanBlockRootsAboveLastAccepted gathers the blocks that may have previously been in processing above the
// last accepted block and wipes their block roots from disk to mark their tries as inaccessible.
// This is used prior to pruning to ensure that all of the tries that may still be in processing are marked
// as inaccessible and mirrors the handling of middle roots in the geth offline pruning implementation.
// This is not strictly necessary, but maintains a soft assumption.
func (bc *BlockChain) CleanBlockRootsAboveLastAccepted() error {
targetRoot := bc.LastAcceptedBlock().Root()

// Clean up any block roots above the last accepted block before we start pruning.
// Note: this takes the place of middleRoots in the geth implementation since we do not
// track processing block roots via snapshot journals in the same way.
processingRoots := bc.gatherBlockRootsAboveLastAccepted()
// If there is a block above the last accepted block with an identical state root, we
// explicitly remove it from the set to ensure we do not corrupt the last accepted trie.
delete(processingRoots, targetRoot)
for processingRoot := range processingRoots {
// Delete the processing root from disk to mark the trie as inaccessible (no need to handle this in a batch).
if err := bc.db.Delete(processingRoot[:]); err != nil {
return fmt.Errorf("failed to remove processing root (%s) preparing for offline pruning: %w", processingRoot, err)
}
}

return nil
}

// gatherBlockRootsAboveLastAccepted iterates forward from the last accepted block and returns a list of all block roots
// for any blocks that were inserted above the last accepted block.
// Given that we never insert a block into the chain unless all of its ancestors have been inserted, this should gather
// all of the block roots for blocks inserted above the last accepted block that may have been in processing at some point
// in the past and are therefore potentially still acceptable.
// Note: there is an edge case where the node dies while the consensus engine is rejecting a branch of blocks since the
// consensus engine will reject the lowest ancestor first. In this case, these blocks will not be considered acceptable in
// the future.
// Ex.
// A
// / \
// B C
// |
// D
// |
// E
// |
// F
//
// The consensus engine accepts block C and proceeds to reject the other branch in order (B, D, E, F).
// If the consensus engine dies after rejecting block D, block D will be deleted, such that the forward iteration
// may not find any blocks at this height and will not reach the previously processing blocks E and F.
func (bc *BlockChain) gatherBlockRootsAboveLastAccepted() map[common.Hash]struct{} {
blockRoots := make(map[common.Hash]struct{})
for height := bc.lastAccepted.NumberU64() + 1; ; height++ {
blockHashes := rawdb.ReadAllHashes(bc.db, height)
// If there are no block hashes at [height], then there should be no further acceptable blocks
// past this point.
if len(blockHashes) == 0 {
break
}

// Fetch the blocks and append their roots.
for _, blockHash := range blockHashes {
block := bc.GetBlockByHash(blockHash)
if block == nil {
continue
}

blockRoots[block.Root()] = struct{}{}
}
}

return blockRoots
}
70 changes: 70 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
package core

import (
"fmt"
"testing"

"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
"github.com/ava-labs/subnet-evm/core/state/pruner"
"github.com/ava-labs/subnet-evm/core/vm"
"github.com/ava-labs/subnet-evm/ethdb"
"github.com/ava-labs/subnet-evm/params"
Expand Down Expand Up @@ -248,3 +250,71 @@ func TestCorruptSnapshots(t *testing.T) {
})
}
}

func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) {
create := func(db ethdb.Database, chainConfig *params.ChainConfig, lastAcceptedHash common.Hash) (*BlockChain, error) {
// Import the chain. This runs all block validation rules.
blockchain, err := NewBlockChain(
db,
&CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
Pruning: true, // Enable pruning
SnapshotLimit: 256,
},
chainConfig,
dummy.NewFaker(),
vm.Config{},
lastAcceptedHash,
)
if err != nil {
return nil, err
}

// Overwrite state manager, so that Shutdown is not called.
// This tests to ensure that the state manager handles an ungraceful shutdown correctly.
blockchain.stateManager = &wrappedStateManager{TrieWriter: blockchain.stateManager}

if lastAcceptedHash == (common.Hash{}) {
return blockchain, nil
}

targetRoot := blockchain.LastAcceptedBlock().Root()
if targetRoot == blockchain.Genesis().Root() {
return blockchain, nil
}

tempDir := t.TempDir()
if err := blockchain.CleanBlockRootsAboveLastAccepted(); err != nil {
return nil, err
}
pruner, err := pruner.NewPruner(db, tempDir, 256)
if err != nil {
return nil, fmt.Errorf("offline pruning failed (%s, %d): %w", tempDir, 256, err)
}

if err := pruner.Prune(targetRoot); err != nil {
return nil, fmt.Errorf("failed to prune blockchain with target root: %s due to: %w", targetRoot, err)
}

// Re-initialize the blockchain after pruning
return NewBlockChain(
db,
&CacheConfig{
TrieCleanLimit: 256,
TrieDirtyLimit: 256,
Pruning: true, // Enable pruning
SnapshotLimit: 256,
},
chainConfig,
dummy.NewFaker(),
vm.Config{},
lastAcceptedHash,
)
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
tt.testFunc(t, create)
})
}
}
12 changes: 7 additions & 5 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,14 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig

// Check config compatibility and write the config. Compatibility errors
// are returned to the caller unless we're already at block zero.
height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db))
if height == nil {
return newcfg, fmt.Errorf("missing block number for head header hash")
headBlock := rawdb.ReadHeadBlock(db)
if headBlock == nil {
return newcfg, fmt.Errorf("missing head block")
}
compatErr := storedcfg.CheckCompatible(newcfg, *height)
if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 {
height := headBlock.NumberU64()
timestamp := headBlock.Time()
compatErr := storedcfg.CheckCompatible(newcfg, height, timestamp)
if compatErr != nil && height != 0 && compatErr.RewindTo != 0 {
return newcfg, compatErr
}
rawdb.WriteChainConfig(db, stored, newcfg)
Expand Down
71 changes: 38 additions & 33 deletions core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"math/big"
"reflect"
"testing"
"time"

"github.com/ava-labs/subnet-evm/consensus/dummy"
"github.com/ava-labs/subnet-evm/core/rawdb"
Expand All @@ -57,23 +56,22 @@ func TestGenesisBlockForTesting(t *testing.T) {
}

func TestSetupGenesis(t *testing.T) {
preSubnetConfig := *params.TestPreSubnetEVMConfig
preSubnetConfig.SubnetEVMTimestamp = big.NewInt(100)
var (
customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50")
customg = Genesis{
Config: &params.ChainConfig{
HomesteadBlock: big.NewInt(0),
SubnetEVMTimestamp: big.NewInt(time.Date(2021, time.July, 31, 14, 0, 0, 0, time.UTC).Unix()),
},
Config: &preSubnetConfig,
Alloc: GenesisAlloc{
{1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}},
},
}
oldcustomg = customg
)
oldcustomg.Config = &params.ChainConfig{
HomesteadBlock: big.NewInt(0),
SubnetEVMTimestamp: big.NewInt(time.Date(2021, time.March, 31, 14, 0, 0, 0, time.UTC).Unix()),
}

rollbackpreSubnetConfig := preSubnetConfig
rollbackpreSubnetConfig.SubnetEVMTimestamp = big.NewInt(90)
oldcustomg.Config = &rollbackpreSubnetConfig
tests := []struct {
name string
fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error)
Expand Down Expand Up @@ -117,46 +115,53 @@ func TestSetupGenesis(t *testing.T) {
wantConfig: customg.Config,
},
{
name: "incompatible config in DB",
name: "incompatible config for avalanche fork in DB",
fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) {
// Commit the 'old' genesis block with SubnetEVM transition at 90.
// Advance to block #4, past the SubnetEVM transition block of customg.
genesis := oldcustomg.MustCommit(db)

bc, _ := NewBlockChain(db, DefaultCacheConfig, oldcustomg.Config, dummy.NewFullFaker(), vm.Config{}, common.Hash{})
defer bc.Stop()
blocks, _, _ := GenerateChain(oldcustomg.Config, genesis, dummy.NewFaker(), db, 4, 10, nil)

blocks, _, _ := GenerateChain(oldcustomg.Config, genesis, dummy.NewFullFaker(), db, 4, 25, nil)
bc.InsertChain(blocks)
bc.CurrentBlock()
// This should return a compatibility error.
return setupGenesisBlock(db, &customg)
},
wantHash: customghash,
wantConfig: customg.Config,
wantErr: &params.ConfigCompatError{
What: "SubnetEVM fork block",
StoredConfig: big.NewInt(1617199200),
NewConfig: big.NewInt(1627740000),
RewindTo: 1617199199,
What: "SubnetEVM fork block timestamp",
StoredConfig: big.NewInt(90),
NewConfig: big.NewInt(100),
RewindTo: 89,
},
},
}

for _, test := range tests {
db := rawdb.NewMemoryDatabase()
config, hash, err := test.fn(db)
// Check the return values.
if !reflect.DeepEqual(err, test.wantErr) {
spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true}
t.Errorf("%s: returned error %#v, want %#v", test.name, spew.NewFormatter(err), spew.NewFormatter(test.wantErr))
}
if !reflect.DeepEqual(config, test.wantConfig) {
t.Errorf("%s:\nreturned %v\nwant %v", test.name, config, test.wantConfig)
}
if hash != test.wantHash {
t.Errorf("%s: returned hash %s, want %s", test.name, hash.Hex(), test.wantHash.Hex())
} else if err == nil {
// Check database content.
stored := rawdb.ReadBlock(db, test.wantHash, 0)
if stored.Hash() != test.wantHash {
t.Errorf("%s: block in DB has hash %s, want %s", test.name, stored.Hash(), test.wantHash)
t.Run(test.name, func(t *testing.T) {
db := rawdb.NewMemoryDatabase()
config, hash, err := test.fn(db)
// Check the return values.
if !reflect.DeepEqual(err, test.wantErr) {
spew := spew.ConfigState{DisablePointerAddresses: true, DisableCapacities: true}
t.Errorf("returned error %#v, want %#v", spew.NewFormatter(err), spew.NewFormatter(test.wantErr))
}
}
if !reflect.DeepEqual(config, test.wantConfig) {
t.Errorf("returned %v\nwant %v", config, test.wantConfig)
}
if hash != test.wantHash {
t.Errorf("returned hash %s, want %s", hash.Hex(), test.wantHash.Hex())
} else if err == nil {
// Check database content.
stored := rawdb.ReadBlock(db, test.wantHash, 0)
if stored.Hash() != test.wantHash {
t.Errorf("block in DB has hash %s, want %s", stored.Hash(), test.wantHash)
}
}
})
}
}
Loading

0 comments on commit 7d22f34

Please sign in to comment.