diff --git a/core/blockchain.go b/core/blockchain.go index fed6b4b74..77295b888 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -20,7 +20,6 @@ package core import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/statediff" "io" "math/big" mrand "math/rand" @@ -45,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/hashicorp/golang-lru" + "github.com/ethereum/go-ethereum/statediff" ) var ( @@ -73,7 +73,7 @@ type CacheConfig struct { TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk - StateDiff bool // Whether or not to calculate and persist state diffs + StateDiff statediff.Config // Settings for state diff extraction } // BlockChain represents the canonical chain given a database with a genesis @@ -177,11 +177,14 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par bc.SetValidator(NewBlockValidator(chainConfig, bc, engine)) bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine)) - if cacheConfig.StateDiff { - bc.diffExtractor = statediff.NewExtractor(db) + var err error + if cacheConfig.StateDiff.On { + bc.diffExtractor, err = statediff.NewExtractor(db, cacheConfig.StateDiff) + if err != nil { + return nil, err + } } - var err error bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt) if err != nil { return nil, err @@ -1214,8 +1217,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty proctime := time.Since(bstart) // If extracting statediffs, do so now - if bc.cacheConfig.StateDiff { - bc.diffExtractor.Extract(*parent, *block) + if bc.cacheConfig.StateDiff.On { + // Currently not doing anything with returned cid... + _, err = bc.diffExtractor.ExtractStateDiff(*parent, *block) + if err != nil { + return i, events, coalescedLogs, err + } } // Write the block to the chain and get the status. diff --git a/statediff/builder.go b/statediff/builder.go index 9017c240d..433353591 100644 --- a/statediff/builder.go +++ b/statediff/builder.go @@ -78,7 +78,7 @@ func (sdb *builder) BuildStateDiff(oldStateRoot, newStateRoot common.Hash, block updatedKeys := findIntersection(createKeys, deleteKeys) // Build and return the statediff - updatedAccounts, err := sdb.buildDiffIncremental(creations, deletions, &updatedKeys) + updatedAccounts, err := sdb.buildDiffIncremental(creations, deletions, updatedKeys) if err != nil { return nil, err } @@ -133,6 +133,7 @@ func (sdb *builder) collectDiffNodes(a, b trie.NodeIterator) (map[common.Address break } } + return diffAccounts, nil } @@ -140,66 +141,63 @@ func (sdb *builder) buildDiffEventual(accounts map[common.Address]*state.Account accountDiffs := make(map[common.Address]AccountDiffEventual) for addr, val := range accounts { sr := val.Root - if storageDiffs, err := sdb.buildStorageDiffsEventual(sr, created); err != nil { + storageDiffs, err := sdb.buildStorageDiffsEventual(sr, created) + if err != nil { log.Error("Failed building eventual storage diffs", "Address", val, "error", err) return nil, err - } else { - code := "" - codeBytes, err := sdb.chainDB.Get(val.CodeHash) - if err == nil && len(codeBytes) != 0 { - code = common.ToHex(codeBytes) - } else { - log.Debug("No code field.", "codehash", val.CodeHash, "Address", val, "error", err) + } + + codeBytes, err := sdb.chainDB.Get(val.CodeHash) + + codeHash := common.ToHex(val.CodeHash) + hexRoot := val.Root.Hex() + + if created { + nonce := diffUint64{ + NewValue: &val.Nonce, } - codeHash := common.ToHex(val.CodeHash) - if created { - nonce := diffUint64{ - NewValue: &val.Nonce, - } - balance := diffBigInt{ - NewValue: val.Balance, - } + balance := diffBigInt{ + NewValue: val.Balance, + } - hexRoot := val.Root.Hex() - contractRoot := diffString{ - NewValue: &hexRoot, - } - accountDiffs[addr] = AccountDiffEventual{ - Nonce: nonce, - Balance: balance, - CodeHash: codeHash, - Code: code, - ContractRoot: contractRoot, - Storage: storageDiffs, - } - } else { - nonce := diffUint64{ - OldValue: &val.Nonce, - } - balance := diffBigInt{ - OldValue: val.Balance, - } - hexRoot := val.Root.Hex() - contractRoot := diffString{ - OldValue: &hexRoot, - } - accountDiffs[addr] = AccountDiffEventual{ - Nonce: nonce, - Balance: balance, - CodeHash: codeHash, - ContractRoot: contractRoot, - Storage: storageDiffs, - } + 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) { +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 { + for _, val := range updatedKeys { createdAcc := creations[common.HexToAddress(val)] deletedAcc := deletions[common.HexToAddress(val)] oldSR := deletedAcc.Root diff --git a/statediff/config.go b/statediff/config.go index ee31f271e..ac207acd1 100644 --- a/statediff/config.go +++ b/statediff/config.go @@ -22,26 +22,27 @@ package statediff import "fmt" type Config struct { - On bool - Mode StateDiffMode + 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 ( - IPFS StateDiffMode = iota + IPLD StateDiffMode = iota LDB SQL ) func (mode StateDiffMode) IsValid() bool { - return mode >= IPFS && mode <= SQL + return mode >= IPLD && mode <= SQL } // String implements the stringer interface. func (mode StateDiffMode) String() string { switch mode { - case IPFS: + case IPLD: return "ipfs" case LDB: return "ldb" @@ -54,7 +55,7 @@ func (mode StateDiffMode) String() string { func (mode StateDiffMode) MarshalText() ([]byte, error) { switch mode { - case IPFS: + case IPLD: return []byte("ipfs"), nil case LDB: return []byte("ldb"), nil @@ -68,7 +69,7 @@ func (mode StateDiffMode) MarshalText() ([]byte, error) { func (mode *StateDiffMode) UnmarshalText(text []byte) error { switch string(text) { case "ipfs": - *mode = IPFS + *mode = IPLD case "ldb": *mode = LDB case "sql": diff --git a/statediff/extractor.go b/statediff/extractor.go index 2abe3ebb3..3cbb5a311 100644 --- a/statediff/extractor.go +++ b/statediff/extractor.go @@ -25,26 +25,31 @@ import ( ) type Extractor interface { - ExtractStateDiff(parent, current types.Block) error + ExtractStateDiff(parent, current types.Block) (string, error) } type extractor struct { - b *builder - p *persister + *builder // Interface for building state diff objects from two blocks + *publisher // Interface for publishing state diff objects to a datastore (e.g. IPFS) } -func NewExtractor(db ethdb.Database) *extractor { - return &extractor{ - b: NewBuilder(db), - p: NewPersister(), - } -} - -func (e *extractor) Extract(parent, current types.Block) error { - stateDiff, err := e.b.BuildStateDiff(parent.Root(), current.Root(), current.Number().Int64(), current.Hash()) +func NewExtractor(db ethdb.Database, config Config) (*extractor, error) { + publisher, err := NewPublisher(config) if err != nil { - return err + return nil, err } - return e.p.PersistStateDiff(stateDiff) + return &extractor{ + builder: NewBuilder(db), + publisher: publisher, + }, nil +} + +func (e *extractor) ExtractStateDiff(parent, current types.Block) (string, error) { + stateDiff, err := e.BuildStateDiff(parent.Root(), current.Root(), current.Number().Int64(), current.Hash()) + if err != nil { + return "", err + } + + return e.PublishStateDiff(stateDiff) } \ No newline at end of file diff --git a/statediff/helpers.go b/statediff/helpers.go index 50626ab2c..8a0a6466e 100644 --- a/statediff/helpers.go +++ b/statediff/helpers.go @@ -112,22 +112,7 @@ func decodeNibbles(nibbles []byte, bytes []byte) { } } -// prefixLen returns the length of the common prefix of a and b. -func prefixLen(a, b []byte) int { - var i, length = 0, len(a) - if len(b) < length { - length = len(b) - } - for ; i < length; i++ { - if a[i] != b[i] { - break - } - } - - return i -} - // hasTerm returns whether a hex key has the terminator flag. func hasTerm(s []byte) bool { return len(s) > 0 && s[len(s)-1] == 16 -} +} \ No newline at end of file diff --git a/statediff/ipfs/adder.go b/statediff/ipfs/adder.go new file mode 100644 index 000000000..23eea1c1f --- /dev/null +++ b/statediff/ipfs/adder.go @@ -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 . + +// 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/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/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 +} \ No newline at end of file diff --git a/statediff/ipfs/dag_putter.go b/statediff/ipfs/dag_putter.go new file mode 100644 index 000000000..92ffa1e8a --- /dev/null +++ b/statediff/ipfs/dag_putter.go @@ -0,0 +1,79 @@ +// 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 . + +// 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" + "github.com/i-norden/go-ethereum/statediff" + + ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format" +) + +const ( + EthStateDiffCode = 0x99 // Register custom codec for state diff? +) + +type DagPutter interface { + DagPut(sd *statediff.StateDiff) (string, error) +} + +type dagPutter struct { + Adder +} + +func NewDagPutter(adder Adder) *dagPutter { + return &dagPutter{Adder: adder} +} + +func (bhdp *dagPutter) DagPut(sd *statediff.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 *statediff.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 +} \ No newline at end of file diff --git a/statediff/persister.go b/statediff/ipfs/helpers.go similarity index 71% rename from statediff/persister.go rename to statediff/ipfs/helpers.go index a3eba0826..a9904afa5 100644 --- a/statediff/persister.go +++ b/statediff/ipfs/helpers.go @@ -17,24 +17,22 @@ // 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 +package ipfs -type Persister interface { - PersistStateDiff(sd *StateDiff) error -} - -type persister struct { - -} - -func NewPersister() *persister { - return &persister{ +import ( + mh "gx/ipfs/QmZyZDi491cCNTLfAhwcaDii2Kg4pwKRkhqQzURGDvY6ua/go-multihash" + "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid" +) +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 } -} - -func (p *persister) PersistStateDiff(sd *StateDiff) error { - //TODO: Persist state diff in IPFS - - return nil + return c, nil } \ No newline at end of file diff --git a/statediff/ipfs/node.go b/statediff/ipfs/node.go new file mode 100644 index 000000000..dd7447a80 --- /dev/null +++ b/statediff/ipfs/node.go @@ -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 . + +// 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 ( + ipld "gx/ipfs/QmWi2BYBL5gJ3CiAiQchg6rn1A8iBsrWy51EYxvHVjFvLb/go-ipld-format" + "gx/ipfs/QmapdYm1b22Frv3k17fqrBYTFRxwiaVJkB299Mfn33edeB/go-cid" + + "github.com/i-norden/go-ethereum/statediff" +) + +type StateDiffNode struct { + *statediff.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") +} \ No newline at end of file diff --git a/statediff/publisher.go b/statediff/publisher.go new file mode 100644 index 000000000..163f8d3b0 --- /dev/null +++ b/statediff/publisher.go @@ -0,0 +1,63 @@ +// 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 . + +// 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 ( + "errors" + "github.com/i-norden/go-ethereum/statediff/ipfs" +) + +type Publisher interface { + PublishStateDiff(sd *StateDiff) (string, error) +} + +type publisher struct { + ipfs.DagPutter + Config +} + +func NewPublisher(config 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 *StateDiff) (string, error) { + switch p.Mode { + case IPLD: + cidStr, err := p.DagPut(sd) + if err != nil { + return "", err + } + + return cidStr, err + case LDB: + case SQL: + default: + } + + return "", errors.New("state diff publisher: unhandled publishing mode") +} \ No newline at end of file diff --git a/statediff/persister_test.go b/statediff/publisher_test.go similarity index 100% rename from statediff/persister_test.go rename to statediff/publisher_test.go diff --git a/statediff/statediff.go b/statediff/struct.go similarity index 98% rename from statediff/statediff.go rename to statediff/struct.go index f2e606936..2142ec755 100644 --- a/statediff/statediff.go +++ b/statediff/struct.go @@ -58,7 +58,7 @@ func (sd *StateDiff) Encode() ([]byte, error) { type AccountDiffEventual struct { Nonce diffUint64 `json:"nonce" gencodec:"required"` Balance diffBigInt `json:"balance" gencodec:"required"` - Code string `json:"code" 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"`