publish statediff to Ipfs #3

Closed
elizabethengelman wants to merge 14 commits from ipfs into master
25 changed files with 1961 additions and 1 deletions

View File

@ -178,6 +178,10 @@ func makeFullNode(ctx *cli.Context) *node.Node {
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
}
if ctx.GlobalBool(utils.StateDiffFlag.Name) {
utils.RegisterStateDiffService(stack)
}
return stack
}

View File

@ -132,6 +132,7 @@ var (
utils.GpoPercentileFlag,
utils.EWASMInterpreterFlag,
utils.EVMInterpreterFlag,
utils.StateDiffFlag,
configFileFlag,
}

View File

@ -245,6 +245,12 @@ var AppHelpFlagGroups = []flagGroup{
utils.MinerLegacyExtraDataFlag,
},
},
{
Name: "STATE DIFF",
Flags: []cli.Flag{
utils.StateDiffFlag,
},
},
{
Name: "MISC",
},

View File

@ -58,6 +58,7 @@ import (
"github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
"gopkg.in/urfave/cli.v1"
"github.com/ethereum/go-ethereum/statediff/service"
)
var (
@ -626,6 +627,12 @@ var (
Usage: "External EVM configuration (default = built-in interpreter)",
Value: "",
}
// Statediff flags
StateDiffFlag = cli.BoolFlag{
Name: "statediff",
Usage: "Enables the calculation of state diffs between each block, persists these state diffs in ipfs",
}
)
// MakeDataDir retrieves the currently requested data directory, terminating
@ -1321,6 +1328,18 @@ func RegisterEthStatsService(stack *node.Node, url string) {
}
}
func RegisterStateDiffService(stack *node.Node) {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
var ethServ *eth.Ethereum
ctx.Service(&ethServ)
chainDb := ethServ.ChainDb()
blockChain := ethServ.BlockChain()
return service.NewStateDiffService(chainDb, blockChain)
}); err != nil {
Fatalf("Failed to register State Diff Service", err)
}
}
func SetupMetrics(ctx *cli.Context) {
if metrics.Enabled {
log.Info("Enabling metrics collection")

View File

@ -174,6 +174,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt)
if err != nil {
return nil, err
@ -1187,6 +1188,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
parent = chain[i-1]
}
state, err := state.New(parent.Root(), bc.stateCache)
if err != nil {
return i, events, coalescedLogs, err
}

View File

@ -154,7 +154,12 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
EWASMInterpreter: config.EWASMInterpreter,
EVMInterpreter: config.EVMInterpreter,
}
cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieCleanLimit: config.TrieCleanCache, TrieDirtyLimit: config.TrieDirtyCache, TrieTimeLimit: config.TrieTimeout}
cacheConfig = &core.CacheConfig{
Disabled: config.NoPruning,
TrieCleanLimit: config.TrieCleanCache,
TrieDirtyLimit: config.TrieDirtyCache,
TrieTimeLimit: config.TrieTimeout,
}
)
eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, eth.chainConfig, eth.engine, vmConfig, eth.shouldPreserve)
if err != nil {

View File

@ -0,0 +1,325 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package builder
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
type Builder interface {
BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber int64, blockHash common.Hash) (*StateDiff, error)
}
type builder struct {
chainDB ethdb.Database
trieDB *trie.Database
cachedTrie *trie.Trie
}
func NewBuilder(db ethdb.Database) *builder {
return &builder{
chainDB: db,
trieDB: trie.NewDatabase(db),
}
}
func (sdb *builder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber int64, blockHash common.Hash) (*StateDiff, error) {
// Generate tries for old and new states
oldTrie, err := trie.New(oldStateRoot, sdb.trieDB)
if err != nil {
log.Debug("error creating oldTrie", err)
return nil, err
}
newTrie, err := trie.New(newStateRoot, sdb.trieDB)
if err != nil {
log.Debug("error creating newTrie", err)
return nil, err
}
// Find created accounts
oldIt := oldTrie.NodeIterator([]byte{})
newIt := newTrie.NodeIterator([]byte{})
creations, err := sdb.collectDiffNodes(oldIt, newIt)
if err != nil {
log.Debug("error collecting creation diff nodes", err)
return nil, err
}
// Find deleted accounts
oldIt = oldTrie.NodeIterator(make([]byte, 0))
newIt = newTrie.NodeIterator(make([]byte, 0))
deletions, err := sdb.collectDiffNodes(newIt, oldIt)
if err != nil {
log.Debug("error collecting deletion diff nodes", err)
return nil, err
}
// Find all the diffed keys
createKeys := sortKeys(creations)
deleteKeys := sortKeys(deletions)
updatedKeys := findIntersection(createKeys, deleteKeys)
// Build and return the statediff
updatedAccounts, err := sdb.buildDiffIncremental(creations, deletions, updatedKeys)
if err != nil {
log.Debug("error building diff incremental for updated", err)
return nil, err
}
createdAccounts, err := sdb.buildDiffEventual(creations, true)
if err != nil {
log.Debug("error building diff incremental for created", err)
return nil, err
}
deletedAccounts, err := sdb.buildDiffEventual(deletions, false)
if err != nil {
log.Debug("error building diff incremental for deleted", err)
return nil, err
}
return &StateDiff{
BlockNumber: blockNumber,
BlockHash: blockHash,
CreatedAccounts: createdAccounts,
DeletedAccounts: deletedAccounts,
UpdatedAccounts: updatedAccounts,
}, nil
}
func (sdb *builder) collectDiffNodes(a, b trie.NodeIterator) (map[common.Address]*state.Account, error) {
var diffAccounts = make(map[common.Address]*state.Account)
it, _ := trie.NewDifferenceIterator(a, b)
for {
log.Debug("Current Path and Hash", "path", pathToStr(it), "hashold", common.Hash(it.Hash()))
if it.Leaf() {
// lookup address
path := make([]byte, len(it.Path())-1)
copy(path, it.Path())
addr, err := sdb.addressByPath(path)
if err != nil {
log.Error("Error looking up address via path", "path", path, "error", err)
return nil, err
}
// lookup account state
var account state.Account
if err := rlp.DecodeBytes(it.LeafBlob(), &account); err != nil {
log.Error("Error looking up account via address", "address", addr, "error", err)
return nil, err
}
// record account to diffs (creation if we are looking at new - old; deletion if old - new)
log.Debug("Account lookup successful", "address", addr, "account", account)
diffAccounts[*addr] = &account
}
cont := it.Next(true)
if !cont {
break
}
}
return diffAccounts, nil
}
func (sdb *builder) buildDiffEventual(accounts map[common.Address]*state.Account, created bool) (map[common.Address]AccountDiffEventual, error) {
accountDiffs := make(map[common.Address]AccountDiffEventual)
for addr, val := range accounts {
sr := val.Root
storageDiffs, err := sdb.buildStorageDiffsEventual(sr, created)
if err != nil {
log.Error("Failed building eventual storage diffs", "Address", val, "error", err)
return nil, err
}
codeBytes, err := sdb.chainDB.Get(val.CodeHash)
codeHash := common.ToHex(val.CodeHash)
hexRoot := val.Root.Hex()
if created {
nonce := DiffUint64{
NewValue: &val.Nonce,
}
balance := DiffBigInt{
NewValue: val.Balance,
}
contractRoot := DiffString{
NewValue: &hexRoot,
}
accountDiffs[addr] = AccountDiffEventual{
Nonce: nonce,
Balance: balance,
CodeHash: codeHash,
Code: codeBytes,
ContractRoot: contractRoot,
Storage: storageDiffs,
}
} else {
nonce := DiffUint64{
OldValue: &val.Nonce,
}
balance := DiffBigInt{
OldValue: val.Balance,
}
contractRoot := DiffString{
OldValue: &hexRoot,
}
accountDiffs[addr] = AccountDiffEventual{
Nonce: nonce,
Balance: balance,
CodeHash: codeHash,
ContractRoot: contractRoot,
Storage: storageDiffs,
}
}
}
return accountDiffs, nil
}
func (sdb *builder) buildDiffIncremental(creations map[common.Address]*state.Account, deletions map[common.Address]*state.Account, updatedKeys []string) (map[common.Address]AccountDiffIncremental, error) {
updatedAccounts := make(map[common.Address]AccountDiffIncremental)
for _, val := range updatedKeys {
createdAcc := creations[common.HexToAddress(val)]
deletedAcc := deletions[common.HexToAddress(val)]
oldSR := deletedAcc.Root
newSR := createdAcc.Root
if storageDiffs, err := sdb.buildStorageDiffsIncremental(oldSR, newSR); err != nil {
log.Error("Failed building storage diffs", "Address", val, "error", err)
return nil, err
} else {
nonce := DiffUint64{
NewValue: &createdAcc.Nonce,
OldValue: &deletedAcc.Nonce,
}
balance := DiffBigInt{
NewValue: createdAcc.Balance,
OldValue: deletedAcc.Balance,
}
codeHash := common.ToHex(createdAcc.CodeHash)
nHexRoot := createdAcc.Root.Hex()
oHexRoot := deletedAcc.Root.Hex()
contractRoot := DiffString{
NewValue: &nHexRoot,
OldValue: &oHexRoot,
}
updatedAccounts[common.HexToAddress(val)] = AccountDiffIncremental{
Nonce: nonce,
Balance: balance,
CodeHash: codeHash,
ContractRoot: contractRoot,
Storage: storageDiffs,
}
delete(creations, common.HexToAddress(val))
delete(deletions, common.HexToAddress(val))
}
}
return updatedAccounts, nil
}
func (sdb *builder) buildStorageDiffsEventual(sr common.Hash, creation bool) (map[string]DiffString, error) {
log.Debug("Storage Root For Eventual Diff", "root", sr.Hex())
sTrie, err := trie.New(sr, sdb.trieDB)
if err != nil {
return nil, err
}
it := sTrie.NodeIterator(make([]byte, 0))
storageDiffs := make(map[string]DiffString)
for {
log.Debug("Iterating over state at path ", "path", pathToStr(it))
if it.Leaf() {
log.Debug("Found leaf in storage", "path", pathToStr(it))
path := pathToStr(it)
value := common.ToHex(it.LeafBlob())
if creation {
storageDiffs[path] = DiffString{NewValue: &value}
} else {
storageDiffs[path] = DiffString{OldValue: &value}
}
}
cont := it.Next(true)
if !cont {
break
}
}
return storageDiffs, nil
}
func (sdb *builder) buildStorageDiffsIncremental(oldSR common.Hash, newSR common.Hash) (map[string]DiffString, error) {
log.Debug("Storage Roots for Incremental Diff", "old", oldSR.Hex(), "new", newSR.Hex())
oldTrie, err := trie.New(oldSR, sdb.trieDB)
if err != nil {
return nil, err
}
newTrie, err := trie.New(newSR, sdb.trieDB)
if err != nil {
return nil, err
}
oldIt := oldTrie.NodeIterator(make([]byte, 0))
newIt := newTrie.NodeIterator(make([]byte, 0))
it, _ := trie.NewDifferenceIterator(oldIt, newIt)
storageDiffs := make(map[string]DiffString)
for {
if it.Leaf() {
log.Debug("Found leaf in storage", "path", pathToStr(it))
path := pathToStr(it)
value := common.ToHex(it.LeafBlob())
if oldVal, err := oldTrie.TryGet(it.LeafKey()); err != nil {
log.Error("Failed to look up value in oldTrie", "path", path, "error", err)
} else {
hexOldVal := common.ToHex(oldVal)
storageDiffs[path] = DiffString{OldValue: &hexOldVal, NewValue: &value}
}
}
cont := it.Next(true)
if !cont {
break
}
}
return storageDiffs, nil
}
func (sdb *builder) addressByPath(path []byte) (*common.Address, error) {
// db := core.PreimageTable(sdb.chainDb)
log.Debug("Looking up address from path", "path", common.ToHex(append([]byte("secure-key-"), path...)))
// if addrBytes,err := db.Get(path); err != nil {
if addrBytes, err := sdb.chainDB.Get(append([]byte("secure-key-"), hexToKeybytes(path)...)); err != nil {
log.Error("Error looking up address via path", "path", common.ToHex(append([]byte("secure-key-"), path...)), "error", err)
return nil, err
} else {
addr := common.BytesToAddress(addrBytes)
log.Debug("Address found", "Address", addr)
return &addr, nil
}
}

View File

@ -0,0 +1,13 @@
package builder_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestBuilder(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Builder Suite")
}

View File

@ -0,0 +1,320 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package builder_test
import (
"github.com/onsi/ginkgo"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
"math/big"
"github.com/ethereum/go-ethereum/crypto"
"github.com/onsi/gomega"
b "github.com/ethereum/go-ethereum/statediff/builder"
)
var (
testdb = ethdb.NewMemDatabase()
testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) //0x71562b71999873DB5b286dF957af199Ec94617F7
testBankFunds = big.NewInt(100000000)
genesis = core.GenesisBlockForTesting(testdb, testBankAddress, testBankFunds)
account1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
account2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
account1Addr = crypto.PubkeyToAddress(account1Key.PublicKey) //0x703c4b2bD70c169f5717101CaeE543299Fc946C7
account2Addr = crypto.PubkeyToAddress(account2Key.PublicKey) //0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
contractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056")
contractAddr common.Address
emptyAccountDiffEventualMap = make(map[common.Address]b.AccountDiffEventual)
emptyAccountDiffIncrementalMap = make(map[common.Address]b.AccountDiffIncremental)
)
/*
contract test {
uint256[100] data;
function Put(uint256 addr, uint256 value) {
data[addr] = value;
}
function Get(uint256 addr) constant returns (uint256 value) {
return data[addr];
}
}
*/
// makeChain creates a chain of n blocks starting at and including parent.
// the returned hash chain is ordered head->parent. In addition, every 3rd block
// contains a transaction and every 5th an uncle to allow testing correct block
// reassembly.
func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) {
blocks, _ := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testdb, n, testChainGen)
hashes := make([]common.Hash, n+1)
hashes[len(hashes)-1] = parent.Hash()
blockm := make(map[common.Hash]*types.Block, n+1)
blockm[parent.Hash()] = parent
for i, b := range blocks {
hashes[len(hashes)-i-2] = b.Hash()
blockm[b.Hash()] = b
}
return hashes, blockm
}
func testChainGen(i int, block *core.BlockGen) {
signer := types.HomesteadSigner{}
switch i {
case 0:
// In block 1, the test bank sends account #1 some ether.
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), account1Addr, big.NewInt(10000), params.TxGas, nil, nil), signer, testBankKey)
block.AddTx(tx)
case 1:
// In block 2, the test bank sends some more ether to account #1.
// account1Addr passes it on to account #2.
// account1Addr creates a test contract.
tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), account1Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, testBankKey)
nonce := block.TxNonce(account1Addr)
tx2, _ := types.SignTx(types.NewTransaction(nonce, account2Addr, big.NewInt(1000), params.TxGas, nil, nil), signer, account1Key)
nonce++
tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, big.NewInt(0), contractCode), signer, account1Key)
contractAddr = crypto.CreateAddress(account1Addr, nonce) //0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592
block.AddTx(tx1)
block.AddTx(tx2)
block.AddTx(tx3)
case 2:
// Block 3 is empty but was mined by account #2.
block.SetCoinbase(account2Addr)
//get function: 60cd2685
//put function: c16431b9
data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003")
tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), contractAddr, big.NewInt(0), 100000, nil, data), signer, testBankKey)
block.AddTx(tx)
}
}
var _ = ginkgo.FDescribe("", func() {
var (
block0Hash, block1Hash, block2Hash, block3Hash common.Hash
block0, block1, block2, block3 *types.Block
builder b.Builder
miningReward = int64(3000000000000000000)
burnAddress = common.HexToAddress("0x0")
diff *b.StateDiff
err error
)
ginkgo.BeforeEach(func() {
_, blocks := makeChain(3, 0, genesis)
block0Hash = common.HexToHash("0xd1721cfd0b29c36fd7a68f25c128e86413fb666a6e1d68e89b875bd299262661")
block1Hash = common.HexToHash("0x47c398dd688eaa4dd11b006888156783fe32df83d59b197c0fcd303408103d39")
block2Hash = common.HexToHash("0x351b2f531838683ba457e8ca4d3a844cc48147dceafbcb589dc6e3227856ee75")
block3Hash = common.HexToHash("0xfa40fbe2d98d98b3363a778d52f2bcd29d6790b9b3f3cab2b167fd12d3550f73")
block0 = blocks[block0Hash]
block1 = blocks[block1Hash]
block2 = blocks[block2Hash]
block3 = blocks[block3Hash]
builder = b.NewBuilder(testdb)
})
ginkgo.It("returns empty account diff collections when the state root hasn't changed", func() {
expectedDiff := b.StateDiff{
BlockNumber: block0.Number().Int64(),
BlockHash: block0Hash,
CreatedAccounts: emptyAccountDiffEventualMap,
DeletedAccounts: emptyAccountDiffEventualMap,
UpdatedAccounts: emptyAccountDiffIncrementalMap,
}
diff, err := builder.BuildStateDiff(block0.Root(), block0.Root(), block0.Number().Int64(), block0Hash)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(diff).To(gomega.Equal(&expectedDiff))
})
ginkgo.Context("Block 1", func() {
//10000 transferred from testBankAddress to account1Addr
var balanceChange = int64(10000)
ginkgo.BeforeEach(func() {
diff, err = builder.BuildStateDiff(block0.Root(), block1.Root(), block1.Number().Int64(), block1Hash)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("includes the block number and hash", func() {
gomega.Expect(diff.BlockNumber).To(gomega.Equal(block1.Number().Int64()))
gomega.Expect(diff.BlockHash).To(gomega.Equal(block1Hash))
})
ginkgo.It("returns an empty collection for deleted accounts", func() {
gomega.Expect(diff.DeletedAccounts).To(gomega.Equal(emptyAccountDiffEventualMap))
})
ginkgo.It("returns balance diffs for updated accounts", func() {
expectedBankBalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(testBankFunds.Int64() - balanceChange),
OldValue: testBankFunds,
}
gomega.Expect(len(diff.UpdatedAccounts)).To(gomega.Equal(1))
gomega.Expect(diff.UpdatedAccounts[testBankAddress].Balance).To(gomega.Equal(expectedBankBalanceDiff))
})
ginkgo.It("returns balance diffs for new accounts", func() {
expectedAccount1BalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(balanceChange),
OldValue: nil,
}
expectedBurnAddrBalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(miningReward),
OldValue: nil,
}
gomega.Expect(len(diff.CreatedAccounts)).To(gomega.Equal(2))
gomega.Expect(diff.CreatedAccounts[account1Addr].Balance).To(gomega.Equal(expectedAccount1BalanceDiff))
gomega.Expect(diff.CreatedAccounts[burnAddress].Balance).To(gomega.Equal(expectedBurnAddrBalanceDiff))
})
})
ginkgo.Context("Block 2", func() {
//1000 transferred from testBankAddress to account1Addr
//1000 transferred from account1Addr to account2Addr
var (
balanceChange = int64(1000)
block1BankBalance = int64(99990000)
block1Account1Balance = int64(10000)
)
ginkgo.BeforeEach(func() {
diff, err = builder.BuildStateDiff(block1.Root(), block2.Root(), block2.Number().Int64(), block2Hash)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("includes the block number and hash", func() {
gomega.Expect(diff.BlockNumber).To(gomega.Equal(block2.Number().Int64()))
gomega.Expect(diff.BlockHash).To(gomega.Equal(block2Hash))
})
ginkgo.It("returns an empty collection for deleted accounts", func() {
gomega.Expect(diff.DeletedAccounts).To(gomega.Equal(emptyAccountDiffEventualMap))
})
ginkgo.It("returns balance diffs for updated accounts", func() {
expectedBankBalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(block1BankBalance - balanceChange),
OldValue: big.NewInt(block1BankBalance),
}
expectedAccount1BalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(block1Account1Balance - balanceChange + balanceChange),
OldValue: big.NewInt(block1Account1Balance),
}
expectedBurnBalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(miningReward + miningReward),
OldValue: big.NewInt(miningReward),
}
gomega.Expect(len(diff.UpdatedAccounts)).To(gomega.Equal(3))
gomega.Expect(diff.UpdatedAccounts[testBankAddress].Balance).To(gomega.Equal(expectedBankBalanceDiff))
gomega.Expect(diff.UpdatedAccounts[account1Addr].Balance).To(gomega.Equal(expectedAccount1BalanceDiff))
gomega.Expect(diff.UpdatedAccounts[burnAddress].Balance).To(gomega.Equal(expectedBurnBalanceDiff))
})
ginkgo.It("returns balance diffs for new accounts", func() {
expectedAccount2BalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(balanceChange),
OldValue: nil,
}
expectedContractBalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(0),
OldValue: nil,
}
gomega.Expect(len(diff.CreatedAccounts)).To(gomega.Equal(2))
gomega.Expect(diff.CreatedAccounts[account2Addr].Balance).To(gomega.Equal(expectedAccount2BalanceDiff))
gomega.Expect(diff.CreatedAccounts[contractAddr].Balance).To(gomega.Equal(expectedContractBalanceDiff))
})
})
ginkgo.Context("Block 3", func() {
//the contract's storage is changed
//and the block is mined by account 2
ginkgo.BeforeEach(func() {
diff, err = builder.BuildStateDiff(block2.Root(), block3.Root(), block3.Number().Int64(), block3Hash)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("includes the block number and hash", func() {
gomega.Expect(diff.BlockNumber).To(gomega.Equal(block3.Number().Int64()))
gomega.Expect(diff.BlockHash).To(gomega.Equal(block3Hash))
})
ginkgo.It("returns an empty collection for deleted accounts", func() {
gomega.Expect(diff.DeletedAccounts).To(gomega.Equal(emptyAccountDiffEventualMap))
})
ginkgo.It("returns an empty collection for created accounts", func() {
gomega.Expect(diff.CreatedAccounts).To(gomega.Equal(emptyAccountDiffEventualMap))
})
ginkgo.It("returns balance, storage and nonce diffs for updated accounts", func() {
block2Account2Balance := int64(1000)
expectedAcct2BalanceDiff := b.DiffBigInt{
NewValue: big.NewInt(block2Account2Balance + miningReward),
OldValue: big.NewInt(block2Account2Balance),
}
expectedContractStorageDiff := make(map[string]b.DiffString)
newVal := "0x03"
oldVal := "0x0"
path := "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace"
expectedContractStorageDiff[path] = b.DiffString{
NewValue: &newVal,
OldValue: &oldVal,
}
oldNonce := uint64(2)
newNonce := uint64(3)
expectedBankNonceDiff := b.DiffUint64{
NewValue: &newNonce,
OldValue: &oldNonce,
}
gomega.Expect(len(diff.UpdatedAccounts)).To(gomega.Equal(3))
gomega.Expect(diff.UpdatedAccounts[account2Addr].Balance).To(gomega.Equal(expectedAcct2BalanceDiff))
gomega.Expect(diff.UpdatedAccounts[contractAddr].Storage).To(gomega.Equal(expectedContractStorageDiff))
gomega.Expect(diff.UpdatedAccounts[testBankAddress].Nonce).To(gomega.Equal(expectedBankNonceDiff))
})
})
})

View File

@ -0,0 +1,118 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package builder
import (
"sort"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/trie"
)
func sortKeys(data map[common.Address]*state.Account) []string {
var keys []string
for key := range data {
keys = append(keys, key.Hex())
}
sort.Strings(keys)
return keys
}
func findIntersection(a, b []string) []string {
lenA := len(a)
lenB := len(b)
iOfA, iOfB := 0, 0
updates := make([]string, 0)
if iOfA >= lenA || iOfB >= lenB {
return updates
}
for {
switch strings.Compare(a[iOfA], b[iOfB]) {
// a[iOfA] < b[iOfB]
case -1:
iOfA++
if iOfA >= lenA {
return updates
}
break
// a[iOfA] == b[iOfB]
case 0:
updates = append(updates, a[iOfA])
iOfA++
iOfB++
if iOfA >= lenA || iOfB >= lenB {
return updates
}
break
// a[iOfA] > b[iOfB]
case 1:
iOfB++
if iOfB >= lenB {
return updates
}
break
}
}
}
func pathToStr(it trie.NodeIterator) string {
path := it.Path()
if hasTerm(path) {
path = path[:len(path)-1]
}
nibblePath := ""
for i, v := range common.ToHex(path) {
if i%2 == 0 && i > 1 {
continue
}
nibblePath = nibblePath + string(v)
}
return nibblePath
}
// Duplicated from trie/encoding.go
func hexToKeybytes(hex []byte) []byte {
if hasTerm(hex) {
hex = hex[:len(hex)-1]
}
if len(hex)&1 != 0 {
panic("can't convert hex key of odd length")
}
key := make([]byte, (len(hex)+1)/2)
decodeNibbles(hex, key)
return key
}
func decodeNibbles(nibbles []byte, bytes []byte) {
for bi, ni := 0, 0; ni < len(nibbles); bi, ni = bi+1, ni+2 {
bytes[bi] = nibbles[ni]<<4 | nibbles[ni+1]
}
}
// hasTerm returns whether a hex key has the terminator flag.
func hasTerm(s []byte) bool {
return len(s) > 0 && s[len(s)-1] == 16
}

View File

@ -0,0 +1,86 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package builder
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
)
type StateDiff struct {
BlockNumber int64 `json:"blockNumber" gencodec:"required"`
BlockHash common.Hash `json:"blockHash" gencodec:"required"`
CreatedAccounts map[common.Address]AccountDiffEventual `json:"createdAccounts" gencodec:"required"`
DeletedAccounts map[common.Address]AccountDiffEventual `json:"deletedAccounts" gencodec:"required"`
UpdatedAccounts map[common.Address]AccountDiffIncremental `json:"updatedAccounts" gencodec:"required"`
encoded []byte
err error
}
func (self *StateDiff) ensureEncoded() {
if self.encoded == nil && self.err == nil {
self.encoded, self.err = json.Marshal(self)
}
}
// Implement Encoder interface for StateDiff
func (sd *StateDiff) Length() int {
sd.ensureEncoded()
return len(sd.encoded)
}
// Implement Encoder interface for StateDiff
func (sd *StateDiff) Encode() ([]byte, error) {
sd.ensureEncoded()
return sd.encoded, sd.err
}
type AccountDiffEventual struct {
Nonce DiffUint64 `json:"nonce" gencodec:"required"`
Balance DiffBigInt `json:"balance" gencodec:"required"`
Code []byte `json:"code" gencodec:"required"`
CodeHash string `json:"codeHash" gencodec:"required"`
ContractRoot DiffString `json:"contractRoot" gencodec:"required"`
Storage map[string]DiffString `json:"storage" gencodec:"required"`
}
type AccountDiffIncremental struct {
Nonce DiffUint64 `json:"nonce" gencodec:"required"`
Balance DiffBigInt `json:"balance" gencodec:"required"`
CodeHash string `json:"codeHash" gencodec:"required"`
ContractRoot DiffString `json:"contractRoot" gencodec:"required"`
Storage map[string]DiffString `json:"storage" gencodec:"required"`
}
type DiffString struct {
NewValue *string `json:"newValue" gencodec:"optional"`
OldValue *string `json:"oldValue" gencodec:"optional"`
}
type DiffUint64 struct {
NewValue *uint64 `json:"newValue" gencodec:"optional"`
OldValue *uint64 `json:"oldValue" gencodec:"optional"`
}
type DiffBigInt struct {
NewValue *big.Int `json:"newValue" gencodec:"optional"`
OldValue *big.Int `json:"oldValue" gencodec:"optional"`
}

88
statediff/config.go Normal file
View File

@ -0,0 +1,88 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package statediff
import "fmt"
type Config struct {
On bool // Whether or not to extract state diffs
Mode StateDiffMode // Mode for storing diffs
Path string // Path for storing diffs
}
type StateDiffMode int
const (
CSV StateDiffMode = iota
IPLD
LDB
SQL
)
func (mode StateDiffMode) IsValid() bool {
return mode >= IPLD && mode <= SQL
}
// String implements the stringer interface.
func (mode StateDiffMode) String() string {
switch mode {
case CSV:
return "csv"
case IPLD:
return "ipfs"
case LDB:
return "ldb"
case SQL:
return "sql"
default:
return "unknown"
}
}
func (mode StateDiffMode) MarshalText() ([]byte, error) {
switch mode {
case CSV:
return []byte("ipfs"), nil
case IPLD:
return []byte("ipfs"), nil
case LDB:
return []byte("ldb"), nil
case SQL:
return []byte("sql"), nil
default:
return nil, fmt.Errorf("unknown state diff storage mode %d", mode)
}
}
func (mode *StateDiffMode) UnmarshalText(text []byte) error {
switch string(text) {
case "csv":
*mode = CSV
case "ipfs":
*mode = IPLD
case "ldb":
*mode = LDB
case "sql":
*mode = SQL
default:
return fmt.Errorf(`unknown state diff storage mode %q, want "ipfs", "ldb" or "sql"`, text)
}
return nil
}

View File

@ -0,0 +1,51 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package extractor
import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/statediff/builder"
"github.com/ethereum/go-ethereum/statediff/publisher"
)
type Extractor interface {
ExtractStateDiff(parent, current types.Block) (string, error)
}
type extractor struct {
Builder builder.Builder // Interface for building state diff objects from two blocks
Publisher publisher.Publisher // Interface for publishing state diff objects to a datastore (e.g. IPFS)
}
func NewExtractor(builder builder.Builder, publisher publisher.Publisher) (*extractor, error) {
return &extractor{
Builder: builder,
Publisher: publisher,
}, nil
}
func (e *extractor) ExtractStateDiff(parent, current types.Block) (string, error) {
stateDiff, err := e.Builder.BuildStateDiff(parent.Root(), current.Root(), current.Number().Int64(), current.Hash())
if err != nil {
return "", err
}
return e.Publisher.PublishStateDiff(stateDiff)
}

View File

@ -0,0 +1,13 @@
package extractor_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestExtractor(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Extractor Suite")
}

View File

@ -0,0 +1,100 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package extractor_test
import (
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
"github.com/ethereum/go-ethereum/core/types"
"math/rand"
"github.com/ethereum/go-ethereum/statediff/testhelpers"
"math/big"
e "github.com/ethereum/go-ethereum/statediff/extractor"
b "github.com/ethereum/go-ethereum/statediff/builder"
)
var _ = ginkgo.Describe("Extractor", func() {
var publisher testhelpers.MockPublisher
var builder testhelpers.MockBuilder
var currentBlockNumber *big.Int
var parentBlock, currentBlock *types.Block
var expectedStateDiff b.StateDiff
var extractor e.Extractor
var err error
ginkgo.BeforeEach(func() {
publisher = testhelpers.MockPublisher{}
builder = testhelpers.MockBuilder{}
extractor, err = e.NewExtractor(&builder, &publisher)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
blockNumber := rand.Int63()
parentBlockNumber := big.NewInt(blockNumber - int64(1))
currentBlockNumber = big.NewInt(blockNumber)
parentBlock = types.NewBlock(&types.Header{Number: parentBlockNumber}, nil, nil, nil)
currentBlock = types.NewBlock(&types.Header{Number: currentBlockNumber}, nil, nil, nil)
expectedStateDiff = b.StateDiff{
BlockNumber: blockNumber,
BlockHash: currentBlock.Hash(),
CreatedAccounts: nil,
DeletedAccounts: nil,
UpdatedAccounts: nil,
}
})
ginkgo.It("builds a state diff struct", func() {
builder.SetStateDiffToBuild(&expectedStateDiff)
_, err = extractor.ExtractStateDiff(*parentBlock, *currentBlock)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(builder.OldStateRoot).To(gomega.Equal(parentBlock.Root()))
gomega.Expect(builder.NewStateRoot).To(gomega.Equal(currentBlock.Root()))
gomega.Expect(builder.BlockNumber).To(gomega.Equal(currentBlockNumber.Int64()))
gomega.Expect(builder.BlockHash).To(gomega.Equal(currentBlock.Hash()))
})
ginkgo.It("returns an error if building the state diff fails", func() {
builder.SetBuilderError(testhelpers.MockError)
_, err = extractor.ExtractStateDiff(*parentBlock, *currentBlock)
gomega.Expect(err).To(gomega.HaveOccurred())
gomega.Expect(err).To(gomega.MatchError(testhelpers.MockError))
})
ginkgo.It("publishes the state diff struct", func() {
builder.SetStateDiffToBuild(&expectedStateDiff)
_, err = extractor.ExtractStateDiff(*parentBlock, *currentBlock)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(publisher.StateDiff).To(gomega.Equal(&expectedStateDiff))
})
ginkgo.It("returns an error if publishing the diff fails", func() {
publisher.SetPublisherError(testhelpers.MockError)
_, err = extractor.ExtractStateDiff(*parentBlock, *currentBlock)
gomega.Expect(err).To(gomega.HaveOccurred())
gomega.Expect(err).To(gomega.MatchError(testhelpers.MockError))
})
})

View File

@ -0,0 +1,58 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package ipfs
import (
"context"
"github.com/ipfs/go-ipfs/core"
"github.com/ipfs/go-ipfs/repo/fsrepo"
ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
)
type Adder interface {
Add(node ipld.Node) error
}
type adder struct {
n *core.IpfsNode
ctx context.Context
}
func (a adder) Add(node ipld.Node) error {
return a.n.DAG.Add(a.n.Context(), node) // For some reason DAG.Add method is not being exposed by the ipld.DAGService
}
func NewAdder(repoPath string) (*adder, error) {
r, err := fsrepo.Open(repoPath)
if err != nil {
return nil, err
}
ctx := context.Background()
cfg := &core.BuildCfg{
Online: false,
Repo: r,
}
ipfsNode, err := core.NewNode(ctx, cfg)
if err != nil {
return nil, err
}
return &adder{n: ipfsNode, ctx: ctx}, nil
}

View File

@ -0,0 +1,80 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package ipfs
import (
"bytes"
"encoding/gob"
ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
"github.com/ethereum/go-ethereum/statediff/builder"
)
const (
EthStateDiffCode = 0x99 // Register custom codec for state diff?
)
type DagPutter interface {
DagPut(sd *builder.StateDiff) (string, error)
}
type dagPutter struct {
Adder
}
func NewDagPutter(adder Adder) *dagPutter {
return &dagPutter{Adder: adder}
}
func (bhdp *dagPutter) DagPut(sd *builder.StateDiff) (string, error) {
nd, err := bhdp.getNode(sd)
if err != nil {
return "", err
}
err = bhdp.Add(nd)
if err != nil {
return "", err
}
return nd.Cid().String(), nil
}
func (bhdp *dagPutter) getNode(sd *builder.StateDiff) (ipld.Node, error) {
var buff bytes.Buffer
enc := gob.NewEncoder(&buff)
err := enc.Encode(sd)
if err != nil {
return nil, err
}
raw := buff.Bytes()
cid, err := RawToCid(EthStateDiffCode, raw)
if err != nil {
return nil, err
}
return &StateDiffNode{
StateDiff: sd,
cid: cid,
rawdata: raw,
}, nil
}

View File

@ -0,0 +1,38 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package ipfs
import (
"gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash"
)
func RawToCid(codec uint64, raw []byte) (*cid.Cid, error) {
c, err := cid.Prefix{
Codec: codec,
Version: 1,
MhType: mh.KECCAK_256,
MhLength: -1,
}.Sum(raw)
if err != nil {
return nil, err
}
return &c, nil
}

View File

@ -0,0 +1,78 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package ipfs
import (
"gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
"github.com/ethereum/go-ethereum/statediff/builder"
)
type StateDiffNode struct {
*builder.StateDiff
cid *cid.Cid
rawdata []byte
}
func (sdn *StateDiffNode) RawData() []byte {
return sdn.rawdata
}
func (sdn *StateDiffNode) Cid() cid.Cid {
return *sdn.cid
}
func (sdn StateDiffNode) String() string {
return sdn.cid.String()
}
func (sdn StateDiffNode) Loggable() map[string]interface{} {
return sdn.cid.Loggable()
}
func (sdn StateDiffNode) Resolve(path []string) (interface{}, []string, error) {
panic("implement me")
}
func (sdn StateDiffNode) Tree(path string, depth int) []string {
panic("implement me")
}
func (sdn StateDiffNode) ResolveLink(path []string) (*ipld.Link, []string, error) {
panic("implement me")
}
func (sdn StateDiffNode) Copy() ipld.Node {
panic("implement me")
}
func (sdn StateDiffNode) Links() []*ipld.Link {
panic("implement me")
}
func (sdn StateDiffNode) Stat() (*ipld.NodeStat, error) {
panic("implement me")
}
func (sdn StateDiffNode) Size() (uint64, error) {
panic("implement me")
}

View File

@ -0,0 +1,199 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Contains a batch of utility type declarations used by the tests. As the node
// operates on unique types, a lot of them are needed to check various features.
package publisher
import (
"encoding/csv"
"github.com/ethereum/go-ethereum/statediff"
"github.com/ethereum/go-ethereum/statediff/builder"
"github.com/ethereum/go-ethereum/statediff/publisher/ipfs"
"os"
"strconv"
"strings"
"time"
)
type Publisher interface {
PublishStateDiff(sd *builder.StateDiff) (string, error)
}
type publisher struct {
ipfs.DagPutter
Config statediff.Config
}
var (
Headers = []string{
"blockNumber", "blockHash", "accountAction",
"code", "codeHash",
"oldNonceValue", "newNonceValue",
"oldBalanceValue", "newBalanceValue",
"oldContractRoot", "newContractRoot",
"storageDiffPaths",
}
timeStampFormat = "20060102150405.00000"
deletedAccountAction = "deleted"
createdAccountAction = "created"
updatedAccountAction = "updated"
)
func NewPublisher(config statediff.Config) (*publisher, error) {
adder, err := ipfs.NewAdder(config.Path)
if err != nil {
return nil, err
}
return &publisher{
DagPutter: ipfs.NewDagPutter(adder),
Config: config,
}, nil
}
func (p *publisher) PublishStateDiff(sd *builder.StateDiff) (string, error) {
switch p.Config.Mode {
case statediff.CSV:
return "", p.publishStateDiffToCSV(*sd)
case statediff.IPLD:
cidStr, err := p.DagPut(sd)
if err != nil {
return "", err
}
return cidStr, err
default:
return "", p.publishStateDiffToCSV(*sd)
}
}
func (p *publisher) publishStateDiffToCSV(sd builder.StateDiff) error {
now := time.Now()
timeStamp := now.Format(timeStampFormat)
filePath := p.Config.Path + timeStamp + ".csv"
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
var data [][]string
data = append(data, Headers)
for _, row := range accumulateCreatedAccountRows(sd) {
data = append(data, row)
}
for _, row := range accumulateUpdatedAccountRows(sd) {
data = append(data, row)
}
for _, row := range accumulateDeletedAccountRows(sd) {
data = append(data, row)
}
for _, value := range data {
err := writer.Write(value)
if err != nil {
return err
}
}
return nil
}
func accumulateUpdatedAccountRows(sd builder.StateDiff) [][]string {
var updatedAccountRows [][]string
for _, accountDiff := range sd.UpdatedAccounts {
formattedAccountData := formatAccountDiffIncremental(accountDiff, sd, updatedAccountAction)
updatedAccountRows = append(updatedAccountRows, formattedAccountData)
}
return updatedAccountRows
}
func accumulateDeletedAccountRows(sd builder.StateDiff) [][]string {
var deletedAccountRows [][]string
for _, accountDiff := range sd.DeletedAccounts {
formattedAccountData := formatAccountDiffEventual(accountDiff, sd, deletedAccountAction)
deletedAccountRows = append(deletedAccountRows, formattedAccountData)
}
return deletedAccountRows
}
func accumulateCreatedAccountRows(sd builder.StateDiff) [][]string {
var createdAccountRows [][]string
for _, accountDiff := range sd.CreatedAccounts {
formattedAccountData := formatAccountDiffEventual(accountDiff, sd, createdAccountAction)
createdAccountRows = append(createdAccountRows, formattedAccountData)
}
return createdAccountRows
}
func formatAccountDiffEventual(accountDiff builder.AccountDiffEventual, sd builder.StateDiff, accountAction string) []string {
oldContractRoot := accountDiff.ContractRoot.OldValue
newContractRoot := accountDiff.ContractRoot.NewValue
var storageDiffPaths []string
for k := range accountDiff.Storage {
storageDiffPaths = append(storageDiffPaths, k)
}
formattedAccountData := []string{
strconv.FormatInt(sd.BlockNumber, 10),
sd.BlockHash.String(),
accountAction,
string(accountDiff.Code),
accountDiff.CodeHash,
strconv.FormatUint(*accountDiff.Nonce.OldValue, 10),
strconv.FormatUint(*accountDiff.Nonce.NewValue, 10),
accountDiff.Balance.OldValue.String(),
accountDiff.Balance.NewValue.String(),
*oldContractRoot,
*newContractRoot,
strings.Join(storageDiffPaths, ","),
}
return formattedAccountData
}
func formatAccountDiffIncremental(accountDiff builder.AccountDiffIncremental, sd builder.StateDiff, accountAction string) []string {
oldContractRoot := accountDiff.ContractRoot.OldValue
newContractRoot := accountDiff.ContractRoot.NewValue
var storageDiffPaths []string
for k := range accountDiff.Storage {
storageDiffPaths = append(storageDiffPaths, k)
}
formattedAccountData := []string{
strconv.FormatInt(sd.BlockNumber, 10),
sd.BlockHash.String(),
accountAction,
"",
accountDiff.CodeHash,
strconv.FormatUint(*accountDiff.Nonce.OldValue, 10),
strconv.FormatUint(*accountDiff.Nonce.NewValue, 10),
accountDiff.Balance.OldValue.String(),
accountDiff.Balance.NewValue.String(),
*oldContractRoot,
*newContractRoot,
strings.Join(storageDiffPaths, ","),
}
return formattedAccountData
}

View File

@ -0,0 +1,13 @@
package publisher_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestPublisher(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Publisher Suite")
}

View File

@ -0,0 +1,207 @@
package publisher_test
import (
"github.com/onsi/ginkgo"
"github.com/ethereum/go-ethereum/statediff"
"github.com/onsi/gomega"
"os"
"encoding/csv"
"github.com/ethereum/go-ethereum/common"
"math/rand"
"math/big"
"path/filepath"
"strings"
"strconv"
p "github.com/ethereum/go-ethereum/statediff/publisher"
"github.com/ethereum/go-ethereum/statediff/builder"
)
var _ = ginkgo.Describe("Publisher", func() {
ginkgo.Context("default CSV publisher", func() {
var (
publisher p.Publisher
err error
config = statediff.Config{
Path: "./test-",
}
)
var (
blockNumber = rand.Int63()
blockHash = "0xfa40fbe2d98d98b3363a778d52f2bcd29d6790b9b3f3cab2b167fd12d3550f73"
codeHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
oldNonceValue = rand.Uint64()
newNonceValue = oldNonceValue + 1
oldBalanceValue = rand.Int63()
newBalanceValue = oldBalanceValue - 1
contractRoot = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
storagePath = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
oldStorage = "0x0"
newStorage = "0x03"
storage = map[string]builder.DiffString{storagePath: {
NewValue: &newStorage,
OldValue: &oldStorage,
}}
address = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592")
createdAccounts = map[common.Address]builder.AccountDiffEventual{address: {
Nonce: builder.DiffUint64{
NewValue: &newNonceValue,
OldValue: &oldNonceValue,
},
Balance: builder.DiffBigInt{
NewValue: big.NewInt(newBalanceValue),
OldValue: big.NewInt(oldBalanceValue),
},
ContractRoot: builder.DiffString{
NewValue: &contractRoot,
OldValue: &contractRoot,
},
Code: []byte("created account code"),
CodeHash: codeHash,
Storage: storage,
}}
updatedAccounts = map[common.Address]builder.AccountDiffIncremental{address: {
Nonce: builder.DiffUint64{
NewValue: &newNonceValue,
OldValue: &oldNonceValue,
},
Balance: builder.DiffBigInt{
NewValue: big.NewInt(newBalanceValue),
OldValue: big.NewInt(oldBalanceValue),
},
CodeHash: codeHash,
ContractRoot: builder.DiffString{
NewValue: &contractRoot,
OldValue: &contractRoot,
},
Storage: storage,
}}
deletedAccounts = map[common.Address]builder.AccountDiffEventual{address: {
Nonce: builder.DiffUint64{
NewValue: &newNonceValue,
OldValue: &oldNonceValue,
},
Balance: builder.DiffBigInt{
NewValue: big.NewInt(newBalanceValue),
OldValue: big.NewInt(oldBalanceValue),
},
ContractRoot: builder.DiffString{
NewValue: &contractRoot,
OldValue: &contractRoot,
},
Code: []byte("deleted account code"),
CodeHash: codeHash,
Storage: storage,
}}
testStateDiff = builder.StateDiff{
BlockNumber: blockNumber,
BlockHash: common.HexToHash(blockHash),
CreatedAccounts: createdAccounts,
DeletedAccounts: deletedAccounts,
UpdatedAccounts: updatedAccounts,
}
)
var lines [][]string
var file *os.File
ginkgo.BeforeEach(func() {
publisher, err = p.NewPublisher(config)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
_, err := publisher.PublishStateDiff(&testStateDiff)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
filePaths := getTestCSVFiles(".")
file, err = os.Open(filePaths[0])
gomega.Expect(err).NotTo(gomega.HaveOccurred())
defer file.Close()
lines, err = csv.NewReader(file).ReadAll()
})
ginkgo.AfterEach(func() {
os.Remove(file.Name())
})
ginkgo.It("persists the column headers to a CSV file", func() {
gomega.Expect(len(lines) > 1).To(gomega.BeTrue())
gomega.Expect(lines[0]).To(gomega.Equal(p.Headers))
})
ginkgo.It("persists the created account diffs to a CSV file", func() {
expectedCreatedAccountRow := []string{
strconv.FormatInt(blockNumber, 10),
blockHash,
"created",
"created account code",
codeHash,
strconv.FormatUint(oldNonceValue, 10),
strconv.FormatUint(newNonceValue, 10),
strconv.FormatInt(oldBalanceValue, 10),
strconv.FormatInt(newBalanceValue, 10),
contractRoot,
contractRoot,
storagePath,
}
gomega.Expect(len(lines) > 1).To(gomega.BeTrue())
gomega.Expect(lines[1]).To(gomega.Equal(expectedCreatedAccountRow))
})
ginkgo.It("persists the updated account diffs to a CSV file", func() {
expectedUpdatedAccountRow := []string{
strconv.FormatInt(blockNumber, 10),
blockHash,
"updated",
"",
codeHash,
strconv.FormatUint(oldNonceValue, 10),
strconv.FormatUint(newNonceValue, 10),
strconv.FormatInt(oldBalanceValue, 10),
strconv.FormatInt(newBalanceValue, 10),
contractRoot,
contractRoot,
storagePath,
}
gomega.Expect(len(lines) > 2).To(gomega.BeTrue())
gomega.Expect(lines[2]).To(gomega.Equal(expectedUpdatedAccountRow))
})
ginkgo.It("persists the deleted account diffs to a CSV file", func() {
expectedDeletedAccountRow := []string{
strconv.FormatInt(blockNumber, 10),
blockHash,
"deleted",
"deleted account code",
codeHash,
strconv.FormatUint(oldNonceValue, 10),
strconv.FormatUint(newNonceValue, 10),
strconv.FormatInt(oldBalanceValue, 10),
strconv.FormatInt(newBalanceValue, 10),
contractRoot,
contractRoot,
storagePath,
}
gomega.Expect(len(lines) > 3).To(gomega.BeTrue())
gomega.Expect(lines[3]).To(gomega.Equal(expectedDeletedAccountRow))
})
})
})
func getTestCSVFiles(rootPath string) []string{
var files []string
err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error {
if strings.HasPrefix(path, "test-") {
files = append(files, path)
}
return nil
})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
return files
}

View File

@ -0,0 +1,74 @@
package service
import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/event"
"log"
e "github.com/ethereum/go-ethereum/statediff/extractor"
b "github.com/ethereum/go-ethereum/statediff/builder"
"github.com/ethereum/go-ethereum/statediff"
p "github.com/ethereum/go-ethereum/statediff/publisher"
)
type StateDiffService struct {
builder *b.Builder
extractor e.Extractor
blockchain *core.BlockChain
}
func NewStateDiffService(db ethdb.Database, blockChain *core.BlockChain) (*StateDiffService, error) {
config := statediff.Config{}
builder := b.NewBuilder(db)
publisher, err := p.NewPublisher(config)
if err != nil {
return nil, nil
}
extractor, _ := e.NewExtractor(builder, publisher)
return &StateDiffService{
blockchain: blockChain,
extractor: extractor,
}, nil
}
func (StateDiffService) Protocols() []p2p.Protocol {
return []p2p.Protocol{}
}
func (StateDiffService) APIs() []rpc.API {
return []rpc.API{}
}
func (sds *StateDiffService) loop (sub event.Subscription, events chan core.ChainHeadEvent) {
defer sub.Unsubscribe()
for {
select {
case ev, ok := <-events:
if !ok {
log.Fatalf("Error getting chain head event from subscription.")
}
log.Println("doing something with an event", ev)
previousBlock := ev.Block
//TODO: figure out the best way to get the previous block
currentBlock := ev.Block
sds.extractor.ExtractStateDiff(*previousBlock, *currentBlock)
}
}
}
func (sds *StateDiffService) Start(server *p2p.Server) error {
events := make(chan core.ChainHeadEvent, 10)
sub := sds.blockchain.SubscribeChainHeadEvent(events)
go sds.loop(sub, events)
return nil
}
func (StateDiffService) Stop() error {
return nil
}

View File

@ -0,0 +1,13 @@
package statediff_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestStatediff(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Statediff Suite")
}

View File

@ -0,0 +1,49 @@
package testhelpers
import (
"github.com/ethereum/go-ethereum/common"
"errors"
"github.com/ethereum/go-ethereum/statediff/builder"
)
var MockError = errors.New("mock error")
type MockBuilder struct {
OldStateRoot common.Hash
NewStateRoot common.Hash
BlockNumber int64
BlockHash common.Hash
stateDiff *builder.StateDiff
builderError error
}
func (builder *MockBuilder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, blockNumber int64, blockHash common.Hash) (*builder.StateDiff, error) {
builder.OldStateRoot = oldStateRoot
builder.NewStateRoot = newStateRoot
builder.BlockNumber = blockNumber
builder.BlockHash = blockHash
return builder.stateDiff, builder.builderError
}
func (builder *MockBuilder) SetStateDiffToBuild(stateDiff *builder.StateDiff) {
builder.stateDiff = stateDiff
}
func (builder *MockBuilder) SetBuilderError(err error) {
builder.builderError = err
}
type MockPublisher struct{
StateDiff *builder.StateDiff
publisherError error
}
func (publisher *MockPublisher) PublishStateDiff(sd *builder.StateDiff) (string, error) {
publisher.StateDiff = sd
return "", publisher.publisherError
}
func (publisher *MockPublisher) SetPublisherError(err error) {
publisher.publisherError = err
}