diff --git a/.travis.yml b/.travis.yml index 966ce52a14..0440afedb0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,13 +18,13 @@ matrix: - os: linux dist: trusty sudo: required - go: "1.10" + go: 1.10.x script: - sudo modprobe fuse - sudo chmod 666 /dev/fuse - sudo chown root:$USER /etc/fuse.conf - go run build/ci.go install - - go run build/ci.go test -coverage + - go run build/ci.go test -coverage $TEST_PACKAGES #- os: osx # go: "1.10" @@ -39,7 +39,7 @@ matrix: # This builder only tests code linters on latest version of Go - os: linux dist: trusty - go: "1.10" + go: 1.10.x env: - lint git: diff --git a/Dockerfile b/Dockerfile index a5f450d19b..edf5a0602d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,11 +12,5 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/ -RUN addgroup -g 1000 geth && \ - adduser -h /root -D -u 1000 -G geth geth && \ - chown geth:geth /root - -USER geth - -EXPOSE 8545 8546 30303 30303/udp 30304/udp +EXPOSE 8545 8546 30303 30303/udp ENTRYPOINT ["geth"] diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 2175edbcb7..e54e107bf3 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -12,10 +12,4 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ -RUN addgroup -g 1000 geth && \ - adduser -h /root -D -u 1000 -G geth geth \ - chown geth:geth /root - -USER geth - -EXPOSE 8545 8546 30303 30303/udp 30304/udp +EXPOSE 8545 8546 30303 30303/udp diff --git a/Makefile b/Makefile index 3922d6015b..5cb9231a18 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,9 @@ ios: test: all build/env.sh go run build/ci.go test +lint: ## Run linters. + build/env.sh go run build/ci.go lint + clean: rm -fr build/_workspace/pkg/ $(GOBIN)/* diff --git a/VERSION b/VERSION index bfa363e76e..53ed4ba51e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.4 +1.8.9 diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 512d8fdfa7..93b513c346 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -111,9 +111,14 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa if err := requireUnpackKind(value, typ, kind, arguments); err != nil { return err } - // If the output interface is a struct, make sure names don't collide + + // If the interface is a struct, get of abi->struct_field mapping + + var abi2struct map[string]string if kind == reflect.Struct { - if err := requireUniqueStructFieldNames(arguments); err != nil { + var err error + abi2struct, err = mapAbiToStructFields(arguments, value) + if err != nil { return err } } @@ -123,9 +128,10 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa switch kind { case reflect.Struct: - err := unpackStruct(value, reflectValue, arg) - if err != nil { - return err + if structField, ok := abi2struct[arg.Name]; ok { + if err := set(value.FieldByName(structField), reflectValue, arg); err != nil { + return err + } } case reflect.Slice, reflect.Array: if value.Len() < i { @@ -151,17 +157,22 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf if len(marshalledValues) != 1 { return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues)) } + elem := reflect.ValueOf(v).Elem() kind := elem.Kind() reflectValue := reflect.ValueOf(marshalledValues[0]) + var abi2struct map[string]string if kind == reflect.Struct { - //make sure names don't collide - if err := requireUniqueStructFieldNames(arguments); err != nil { + var err error + if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil { return err } - - return unpackStruct(elem, reflectValue, arguments[0]) + arg := arguments.NonIndexed()[0] + if structField, ok := abi2struct[arg.Name]; ok { + return set(elem.FieldByName(structField), reflectValue, arg) + } + return nil } return set(elem, reflectValue, arguments.NonIndexed()[0]) @@ -277,18 +288,3 @@ func capitalise(input string) string { } return strings.ToUpper(input[:1]) + input[1:] } - -//unpackStruct extracts each argument into its corresponding struct field -func unpackStruct(value, reflectValue reflect.Value, arg Argument) error { - name := capitalise(arg.Name) - typ := value.Type() - for j := 0; j < typ.NumField(); j++ { - // TODO read tags: `abi:"fieldName"` - if typ.Field(j).Name == name { - if err := set(value.Field(j), reflectValue, arg); err != nil { - return err - } - } - } - return nil -} diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 2b5c5fc4a4..7b605e1c1e 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -65,7 +66,7 @@ type SimulatedBackend struct { // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend { - database, _ := ethdb.NewMemDatabase() + database := ethdb.NewMemDatabase() genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc} genesis.MustCommit(database) blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}) @@ -159,7 +160,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres // TransactionReceipt returns the receipt of a transaction. func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - receipt, _, _, _ := core.GetReceipt(b.database, txHash) + receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash) return receipt, nil } @@ -430,11 +431,19 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb } func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil + number := rawdb.ReadHeaderNumber(fb.db, hash) + if number == nil { + return nil, nil + } + return rawdb.ReadReceipts(fb.db, hash, *number), nil } func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { - receipts := core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)) + number := rawdb.ReadHeaderNumber(fb.db, hash) + if number == nil { + return nil, nil + } + receipts := rawdb.ReadReceipts(fb.db, hash, *number) if receipts == nil { return nil, nil } diff --git a/accounts/abi/event.go b/accounts/abi/event.go index 595f169f3a..a3f6be9732 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -33,15 +33,15 @@ type Event struct { Inputs Arguments } -func (event Event) String() string { - inputs := make([]string, len(event.Inputs)) - for i, input := range event.Inputs { +func (e Event) String() string { + inputs := make([]string, len(e.Inputs)) + for i, input := range e.Inputs { inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type) if input.Indexed { inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type) } } - return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", ")) + return fmt.Sprintf("e %v(%v)", e.Name, strings.Join(inputs, ", ")) } // Id returns the canonical representation of the event's signature used by the diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index cca61e433d..3bfdd7c0ab 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -58,12 +58,28 @@ var jsonEventPledge = []byte(`{ "type": "event" }`) +var jsonEventMixedCase = []byte(`{ + "anonymous": false, + "inputs": [{ + "indexed": false, "name": "value", "type": "uint256" + }, { + "indexed": false, "name": "_value", "type": "uint256" + }, { + "indexed": false, "name": "Value", "type": "uint256" + }], + "name": "MixedCase", + "type": "event" + }`) + // 1000000 var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240" // "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd" var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000" +// 1000000,2218516807680,1000001 +var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241" + func TestEventId(t *testing.T) { var table = []struct { definition string @@ -121,6 +137,27 @@ func TestEventTupleUnpack(t *testing.T) { Value *big.Int } + type EventTransferWithTag struct { + // this is valid because `value` is not exportable, + // so value is only unmarshalled into `Value1`. + value *big.Int + Value1 *big.Int `abi:"value"` + } + + type BadEventTransferWithSameFieldAndTag struct { + Value *big.Int + Value1 *big.Int `abi:"value"` + } + + type BadEventTransferWithDuplicatedTag struct { + Value1 *big.Int `abi:"value"` + Value2 *big.Int `abi:"value"` + } + + type BadEventTransferWithEmptyTag struct { + Value *big.Int `abi:""` + } + type EventPledge struct { Who common.Address Wad *big.Int @@ -133,9 +170,16 @@ func TestEventTupleUnpack(t *testing.T) { Currency [3]byte } + type EventMixedCase struct { + Value1 *big.Int `abi:"value"` + Value2 *big.Int `abi:"_value"` + Value3 *big.Int `abi:"Value"` + } + bigint := new(big.Int) bigintExpected := big.NewInt(1000000) bigintExpected2 := big.NewInt(2218516807680) + bigintExpected3 := big.NewInt(1000001) addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268") var testCases = []struct { data string @@ -158,6 +202,34 @@ func TestEventTupleUnpack(t *testing.T) { jsonEventTransfer, "", "Can unpack ERC20 Transfer event into slice", + }, { + transferData1, + &EventTransferWithTag{}, + &EventTransferWithTag{Value1: bigintExpected}, + jsonEventTransfer, + "", + "Can unpack ERC20 Transfer event into structure with abi: tag", + }, { + transferData1, + &BadEventTransferWithDuplicatedTag{}, + &BadEventTransferWithDuplicatedTag{}, + jsonEventTransfer, + "struct: abi tag in 'Value2' already mapped", + "Can not unpack ERC20 Transfer event with duplicated abi tag", + }, { + transferData1, + &BadEventTransferWithSameFieldAndTag{}, + &BadEventTransferWithSameFieldAndTag{}, + jsonEventTransfer, + "abi: multiple variables maps to the same abi field 'value'", + "Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable", + }, { + transferData1, + &BadEventTransferWithEmptyTag{}, + &BadEventTransferWithEmptyTag{}, + jsonEventTransfer, + "struct: abi tag in 'Value' is empty", + "Can not unpack ERC20 Transfer event with an empty tag", }, { pledgeData1, &EventPledge{}, @@ -216,6 +288,13 @@ func TestEventTupleUnpack(t *testing.T) { jsonEventPledge, "abi: cannot unmarshal tuple into map[string]interface {}", "Can not unpack Pledge event into map", + }, { + mixedCaseData1, + &EventMixedCase{}, + &EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3}, + jsonEventMixedCase, + "", + "Can unpack abi variables with mixed case", }} for _, tc := range testCases { @@ -227,7 +306,7 @@ func TestEventTupleUnpack(t *testing.T) { assert.Nil(err, "Should be able to unpack event data.") assert.Equal(tc.expected, tc.dest, tc.name) } else { - assert.EqualError(err, tc.error) + assert.EqualError(err, tc.error, tc.name) } }) } diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 5620a70845..0193517a42 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -19,6 +19,7 @@ package abi import ( "fmt" "reflect" + "strings" ) // indirect recursively dereferences the value until it either gets the value @@ -111,18 +112,101 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind, return nil } -// requireUniqueStructFieldNames makes sure field names don't collide -func requireUniqueStructFieldNames(args Arguments) error { - exists := make(map[string]bool) - for _, arg := range args { - field := capitalise(arg.Name) - if field == "" { - return fmt.Errorf("abi: purely underscored output cannot unpack to struct") +// mapAbiToStringField maps abi to struct fields. +// first round: for each Exportable field that contains a `abi:""` tag +// and this field name exists in the arguments, pair them together. +// second round: for each argument field that has not been already linked, +// find what variable is expected to be mapped into, if it exists and has not been +// used, pair them. +func mapAbiToStructFields(args Arguments, value reflect.Value) (map[string]string, error) { + + typ := value.Type() + + abi2struct := make(map[string]string) + struct2abi := make(map[string]string) + + // first round ~~~ + for i := 0; i < typ.NumField(); i++ { + structFieldName := typ.Field(i).Name + + // skip private struct fields. + if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) { + continue } - if exists[field] { - return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field) + + // skip fields that have no abi:"" tag. + var ok bool + var tagName string + if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok { + continue } - exists[field] = true + + // check if tag is empty. + if tagName == "" { + return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName) + } + + // check which argument field matches with the abi tag. + found := false + for _, abiField := range args.NonIndexed() { + if abiField.Name == tagName { + if abi2struct[abiField.Name] != "" { + return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName) + } + // pair them + abi2struct[abiField.Name] = structFieldName + struct2abi[structFieldName] = abiField.Name + found = true + } + } + + // check if this tag has been mapped. + if !found { + return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName) + } + } - return nil + + // second round ~~~ + for _, arg := range args { + + abiFieldName := arg.Name + structFieldName := capitalise(abiFieldName) + + if structFieldName == "" { + return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct") + } + + // this abi has already been paired, skip it... unless there exists another, yet unassigned + // struct field with the same field name. If so, raise an error: + // abi: [ { "name": "value" } ] + // struct { Value *big.Int , Value1 *big.Int `abi:"value"`} + if abi2struct[abiFieldName] != "" { + if abi2struct[abiFieldName] != structFieldName && + struct2abi[structFieldName] == "" && + value.FieldByName(structFieldName).IsValid() { + return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", abiFieldName) + } + continue + } + + // return an error if this struct field has already been paired. + if struct2abi[structFieldName] != "" { + return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName) + } + + if value.FieldByName(structFieldName).IsValid() { + // pair them + abi2struct[abiFieldName] = structFieldName + struct2abi[structFieldName] = abiFieldName + } else { + // not paired, but annotate as used, to detect cases like + // abi : [ { "name": "value" }, { "name": "_value" } ] + // struct { Value *big.Int } + struct2abi[structFieldName] = abiFieldName + } + + } + + return abi2struct, nil } diff --git a/accounts/keystore/keystore_passphrase.go b/accounts/keystore/keystore_passphrase.go index eaec39f7df..da632fe345 100644 --- a/accounts/keystore/keystore_passphrase.go +++ b/accounts/keystore/keystore_passphrase.go @@ -108,9 +108,8 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er func (ks keyStorePassphrase) JoinPath(filename string) string { if filepath.IsAbs(filename) { return filename - } else { - return filepath.Join(ks.keysDirPath, filename) } + return filepath.Join(ks.keysDirPath, filename) } // EncryptKey encrypts a key using the specified scrypt parameters into a json diff --git a/accounts/keystore/keystore_plain.go b/accounts/keystore/keystore_plain.go index b490ca72b8..f62a133ce1 100644 --- a/accounts/keystore/keystore_plain.go +++ b/accounts/keystore/keystore_plain.go @@ -56,7 +56,6 @@ func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error { func (ks keyStorePlain) JoinPath(filename string) string { if filepath.IsAbs(filename) { return filename - } else { - return filepath.Join(ks.keysDirPath, filename) } + return filepath.Join(ks.keysDirPath, filename) } diff --git a/accounts/url.go b/accounts/url.go index 47f9d8ee4b..21df668efd 100644 --- a/accounts/url.go +++ b/accounts/url.go @@ -74,6 +74,22 @@ func (u URL) MarshalJSON() ([]byte, error) { return json.Marshal(u.String()) } +// UnmarshalJSON parses url. +func (u *URL) UnmarshalJSON(input []byte) error { + var textUrl string + err := json.Unmarshal(input, &textUrl) + if err != nil { + return err + } + url, err := parseURL(textUrl) + if err != nil { + return err + } + u.Scheme = url.Scheme + u.Path = url.Path + return nil +} + // Cmp compares x and y and returns: // // -1 if x < y diff --git a/accounts/usbwallet/hub.go b/accounts/usbwallet/hub.go index 61fc98ccc8..640320bc91 100644 --- a/accounts/usbwallet/hub.go +++ b/accounts/usbwallet/hub.go @@ -127,7 +127,7 @@ func (hub *Hub) refreshWallets() { // breaking the Ledger protocol if that is waiting for user confirmation. This // is a bug acknowledged at Ledger, but it won't be fixed on old devices so we // need to prevent concurrent comms ourselves. The more elegant solution would - // be to ditch enumeration in favor of hutplug events, but that don't work yet + // be to ditch enumeration in favor of hotplug events, but that don't work yet // on Windows so if we need to hack it anyway, this is more elegant for now. hub.commsLock.Lock() if hub.commsPend > 0 { // A confirmation is pending, don't refresh diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index f5def61d23..7ad32dd1e8 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -53,11 +53,9 @@ const ( ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet - ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address - ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address ) // errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 8b3b5a5224..6cef6e0fb0 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -99,7 +99,7 @@ type wallet struct { // // As such, a hardware wallet needs two locks to function correctly. A state // lock can be used to protect the wallet's software-side internal state, which - // must not be held exlusively during hardware communication. A communication + // must not be held exclusively during hardware communication. A communication // lock can be used to achieve exclusive access to the device itself, this one // however should allow "skipping" waiting for operations that might want to // use the device, but can live without too (e.g. account self-derivation). diff --git a/appveyor.yml b/appveyor.yml index 45475d1669..141ef16ff8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,8 +23,8 @@ environment: install: - git submodule update --init - rmdir C:\go /s /q - - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.windows-%GETH_ARCH%.zip - - 7z x go1.10.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.1.windows-%GETH_ARCH%.zip + - 7z x go1.10.1.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/bmt/bmt.go b/bmt/bmt.go index 4b65b1d94a..c290223452 100644 --- a/bmt/bmt.go +++ b/bmt/bmt.go @@ -75,7 +75,7 @@ type Hasher struct { blocksize int // segment size (size of hash) also for hash.Hash count int // segment count size int // for hash.Hash same as hashsize - cur int // cursor position for righmost currently open chunk + cur int // cursor position for rightmost currently open chunk segment []byte // the rightmost open segment (not complete) depth int // index of last level result chan []byte // result channel @@ -149,30 +149,30 @@ func NewTreePool(hasher BaseHasher, segmentCount, capacity int) *TreePool { } } -// Drain drains the pool uptil it has no more than n resources -func (self *TreePool) Drain(n int) { - self.lock.Lock() - defer self.lock.Unlock() - for len(self.c) > n { - <-self.c - self.count-- +// Drain drains the pool until it has no more than n resources +func (p *TreePool) Drain(n int) { + p.lock.Lock() + defer p.lock.Unlock() + for len(p.c) > n { + <-p.c + p.count-- } } // Reserve is blocking until it returns an available Tree // it reuses free Trees or creates a new one if size is not reached -func (self *TreePool) Reserve() *Tree { - self.lock.Lock() - defer self.lock.Unlock() +func (p *TreePool) Reserve() *Tree { + p.lock.Lock() + defer p.lock.Unlock() var t *Tree - if self.count == self.Capacity { - return <-self.c + if p.count == p.Capacity { + return <-p.c } select { - case t = <-self.c: + case t = <-p.c: default: - t = NewTree(self.hasher, self.SegmentSize, self.SegmentCount) - self.count++ + t = NewTree(p.hasher, p.SegmentSize, p.SegmentCount) + p.count++ } return t } @@ -180,8 +180,8 @@ func (self *TreePool) Reserve() *Tree { // Release gives back a Tree to the pool. // This Tree is guaranteed to be in reusable state // does not need locking -func (self *TreePool) Release(t *Tree) { - self.c <- t // can never fail but... +func (p *TreePool) Release(t *Tree) { + p.c <- t // can never fail but... } // Tree is a reusable control structure representing a BMT @@ -193,17 +193,17 @@ type Tree struct { } // Draw draws the BMT (badly) -func (self *Tree) Draw(hash []byte, d int) string { +func (t *Tree) Draw(hash []byte, d int) string { var left, right []string var anc []*Node - for i, n := range self.leaves { + for i, n := range t.leaves { left = append(left, fmt.Sprintf("%v", hashstr(n.left))) if i%2 == 0 { anc = append(anc, n.parent) } right = append(right, fmt.Sprintf("%v", hashstr(n.right))) } - anc = self.leaves + anc = t.leaves var hashes [][]string for l := 0; len(anc) > 0; l++ { var nodes []*Node @@ -277,42 +277,42 @@ func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree { // methods needed by hash.Hash // Size returns the size -func (self *Hasher) Size() int { - return self.size +func (h *Hasher) Size() int { + return h.size } // BlockSize returns the block size -func (self *Hasher) BlockSize() int { - return self.blocksize +func (h *Hasher) BlockSize() int { + return h.blocksize } // Sum returns the hash of the buffer // hash.Hash interface Sum method appends the byte slice to the underlying // data before it calculates and returns the hash of the chunk -func (self *Hasher) Sum(b []byte) (r []byte) { - t := self.bmt - i := self.cur +func (h *Hasher) Sum(b []byte) (r []byte) { + t := h.bmt + i := h.cur n := t.leaves[i] j := i // must run strictly before all nodes calculate // datanodes are guaranteed to have a parent - if len(self.segment) > self.size && i > 0 && n.parent != nil { + if len(h.segment) > h.size && i > 0 && n.parent != nil { n = n.parent } else { i *= 2 } - d := self.finalise(n, i) - self.writeSegment(j, self.segment, d) - c := <-self.result - self.releaseTree() + d := h.finalise(n, i) + h.writeSegment(j, h.segment, d) + c := <-h.result + h.releaseTree() // sha3(length + BMT(pure_chunk)) - if self.blockLength == nil { + if h.blockLength == nil { return c } - res := self.pool.hasher() + res := h.pool.hasher() res.Reset() - res.Write(self.blockLength) + res.Write(h.blockLength) res.Write(c) return res.Sum(nil) } @@ -321,8 +321,8 @@ func (self *Hasher) Sum(b []byte) (r []byte) { // Hash waits for the hasher result and returns it // caller must call this on a BMT Hasher being written to -func (self *Hasher) Hash() []byte { - return <-self.result +func (h *Hasher) Hash() []byte { + return <-h.result } // Hasher implements the io.Writer interface @@ -330,16 +330,16 @@ func (self *Hasher) Hash() []byte { // Write fills the buffer to hash // with every full segment complete launches a hasher go routine // that shoots up the BMT -func (self *Hasher) Write(b []byte) (int, error) { +func (h *Hasher) Write(b []byte) (int, error) { l := len(b) if l <= 0 { return 0, nil } - s := self.segment - i := self.cur - count := (self.count + 1) / 2 - need := self.count*self.size - self.cur*2*self.size - size := self.size + s := h.segment + i := h.cur + count := (h.count + 1) / 2 + need := h.count*h.size - h.cur*2*h.size + size := h.size if need > size { size *= 2 } @@ -356,7 +356,7 @@ func (self *Hasher) Write(b []byte) (int, error) { // read full segments and the last possibly partial segment for need > 0 && i < count-1 { // push all finished chunks we read - self.writeSegment(i, s, self.depth) + h.writeSegment(i, s, h.depth) need -= size if need < 0 { size += need @@ -365,8 +365,8 @@ func (self *Hasher) Write(b []byte) (int, error) { rest += size i++ } - self.segment = s - self.cur = i + h.segment = s + h.cur = i // otherwise, we can assume len(s) == 0, so all buffer is read and chunk is not yet full return l, nil } @@ -376,8 +376,8 @@ func (self *Hasher) Write(b []byte) (int, error) { // ReadFrom reads from io.Reader and appends to the data to hash using Write // it reads so that chunk to hash is maximum length or reader reaches EOF // caller must Reset the hasher prior to call -func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) { - bufsize := self.size*self.count - self.size*self.cur - len(self.segment) +func (h *Hasher) ReadFrom(r io.Reader) (m int64, err error) { + bufsize := h.size*h.count - h.size*h.cur - len(h.segment) buf := make([]byte, bufsize) var read int for { @@ -385,7 +385,7 @@ func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) { n, err = r.Read(buf) read += n if err == io.EOF || read == len(buf) { - hash := self.Sum(buf[:n]) + hash := h.Sum(buf[:n]) if read == len(buf) { err = NewEOC(hash) } @@ -394,7 +394,7 @@ func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) { if err != nil { break } - n, err = self.Write(buf[:n]) + n, err = h.Write(buf[:n]) if err != nil { break } @@ -403,63 +403,62 @@ func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) { } // Reset needs to be called before writing to the hasher -func (self *Hasher) Reset() { - self.getTree() - self.blockLength = nil +func (h *Hasher) Reset() { + h.getTree() + h.blockLength = nil } // Hasher implements the SwarmHash interface // ResetWithLength needs to be called before writing to the hasher // the argument is supposed to be the byte slice binary representation of -// the legth of the data subsumed under the hash -func (self *Hasher) ResetWithLength(l []byte) { - self.Reset() - self.blockLength = l - +// the length of the data subsumed under the hash +func (h *Hasher) ResetWithLength(l []byte) { + h.Reset() + h.blockLength = l } // Release gives back the Tree to the pool whereby it unlocks // it resets tree, segment and index -func (self *Hasher) releaseTree() { - if self.bmt != nil { - n := self.bmt.leaves[self.cur] +func (h *Hasher) releaseTree() { + if h.bmt != nil { + n := h.bmt.leaves[h.cur] for ; n != nil; n = n.parent { n.unbalanced = false if n.parent != nil { n.root = false } } - self.pool.Release(self.bmt) - self.bmt = nil + h.pool.Release(h.bmt) + h.bmt = nil } - self.cur = 0 - self.segment = nil + h.cur = 0 + h.segment = nil } -func (self *Hasher) writeSegment(i int, s []byte, d int) { - h := self.pool.hasher() - n := self.bmt.leaves[i] +func (h *Hasher) writeSegment(i int, s []byte, d int) { + hash := h.pool.hasher() + n := h.bmt.leaves[i] - if len(s) > self.size && n.parent != nil { + if len(s) > h.size && n.parent != nil { go func() { - h.Reset() - h.Write(s) - s = h.Sum(nil) + hash.Reset() + hash.Write(s) + s = hash.Sum(nil) if n.root { - self.result <- s + h.result <- s return } - self.run(n.parent, h, d, n.index, s) + h.run(n.parent, hash, d, n.index, s) }() return } - go self.run(n, h, d, i*2, s) + go h.run(n, hash, d, i*2, s) } -func (self *Hasher) run(n *Node, h hash.Hash, d int, i int, s []byte) { +func (h *Hasher) run(n *Node, hash hash.Hash, d int, i int, s []byte) { isLeft := i%2 == 0 for { if isLeft { @@ -471,18 +470,18 @@ func (self *Hasher) run(n *Node, h hash.Hash, d int, i int, s []byte) { return } if !n.unbalanced || !isLeft || i == 0 && d == 0 { - h.Reset() - h.Write(n.left) - h.Write(n.right) - s = h.Sum(nil) + hash.Reset() + hash.Write(n.left) + hash.Write(n.right) + s = hash.Sum(nil) } else { s = append(n.left, n.right...) } - self.hash = s + h.hash = s if n.root { - self.result <- s + h.result <- s return } @@ -493,20 +492,20 @@ func (self *Hasher) run(n *Node, h hash.Hash, d int, i int, s []byte) { } // getTree obtains a BMT resource by reserving one from the pool -func (self *Hasher) getTree() *Tree { - if self.bmt != nil { - return self.bmt +func (h *Hasher) getTree() *Tree { + if h.bmt != nil { + return h.bmt } - t := self.pool.Reserve() - self.bmt = t + t := h.pool.Reserve() + h.bmt = t return t } // atomic bool toggle implementing a concurrent reusable 2-state object // atomic addint with %2 implements atomic bool toggle // it returns true if the toggler just put it in the active/waiting state -func (self *Node) toggle() bool { - return atomic.AddInt32(&self.state, 1)%2 == 1 +func (n *Node) toggle() bool { + return atomic.AddInt32(&n.state, 1)%2 == 1 } func hashstr(b []byte) string { @@ -526,12 +525,12 @@ func depth(n int) (d int) { // finalise is following the zigzags on the tree belonging // to the final datasegment -func (self *Hasher) finalise(n *Node, i int) (d int) { +func (h *Hasher) finalise(n *Node, i int) (d int) { isLeft := i%2 == 0 for { // when the final segment's path is going via left segments // the incoming data is pushed to the parent upon pulling the left - // we do not need toogle the state since this condition is + // we do not need toggle the state since this condition is // detectable n.unbalanced = isLeft n.right = nil @@ -551,8 +550,8 @@ type EOC struct { } // Error returns the error string -func (self *EOC) Error() string { - return fmt.Sprintf("hasher limit reached, chunk hash: %x", self.Hash) +func (e *EOC) Error() string { + return fmt.Sprintf("hasher limit reached, chunk hash: %x", e.Hash) } // NewEOC creates new end of chunk error with the hash diff --git a/build/ci.go b/build/ci.go index 1881a596e9..79dcc146c3 100644 --- a/build/ci.go +++ b/build/ci.go @@ -329,7 +329,10 @@ func doLint(cmdline []string) { // Run fast linters batched together configs := []string{ "--vendor", + "--tests", "--disable-all", + "--enable=goimports", + "--enable=varcheck", "--enable=vet", "--enable=gofmt", "--enable=misspell", @@ -340,7 +343,7 @@ func doLint(cmdline []string) { // Run slow linters one by one for _, linter := range []string{"unconvert", "gosimple"} { - configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter} + configs = []string{"--vendor", "--tests", "--deadline=10m", "--disable-all", "--enable=" + linter} build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...) } } @@ -728,7 +731,7 @@ func doAndroidArchive(cmdline []string) { // Build the Android archive and Maven resources build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK"))) - build.MustRun(gomobileTool("bind", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile")) + build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile")) if *local { // If we're building locally, copy bundle to build dir and skip Maven @@ -752,22 +755,27 @@ func doAndroidArchive(cmdline []string) { os.Rename(archive, meta.Package+".aar") if *signer != "" && *deploy != "" { // Import the signing key into the local GPG instance - if b64key := os.Getenv(*signer); b64key != "" { - key, err := base64.StdEncoding.DecodeString(b64key) - if err != nil { - log.Fatalf("invalid base64 %s", *signer) - } - gpg := exec.Command("gpg", "--import") - gpg.Stdin = bytes.NewReader(key) - build.MustRun(gpg) + b64key := os.Getenv(*signer) + key, err := base64.StdEncoding.DecodeString(b64key) + if err != nil { + log.Fatalf("invalid base64 %s", *signer) + } + gpg := exec.Command("gpg", "--import") + gpg.Stdin = bytes.NewReader(key) + build.MustRun(gpg) + + keyID, err := build.PGPKeyID(string(key)) + if err != nil { + log.Fatal(err) } // Upload the artifacts to Sonatype and/or Maven Central repo := *deploy + "/service/local/staging/deploy/maven2" if meta.Develop { repo = *deploy + "/content/repositories/snapshots" } - build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", + build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X", "-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh", + "-Dgpg.keyname="+keyID, "-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar") } } @@ -849,7 +857,7 @@ func doXCodeFramework(cmdline []string) { // Build the iOS XCode framework build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) build.MustRun(gomobileTool("init")) - bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "-v", "github.com/ethereum/go-ethereum/mobile") + bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "--tags", "ios", "-v", "github.com/ethereum/go-ethereum/mobile") if *local { // If we're building locally, use the build folder and stop afterwards diff --git a/build/goimports.sh b/build/goimports.sh new file mode 100755 index 0000000000..6d67ef1f0f --- /dev/null +++ b/build/goimports.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +find_files() { + find . -not \( \ + \( \ + -wholename '.github' \ + -o -wholename './build/_workspace' \ + -o -wholename './build/bin' \ + -o -wholename './crypto/bn256' \ + -o -wholename '*/vendor/*' \ + \) -prune \ + \) -name '*.go' +} + +GOFMT="gofmt -s -w"; +GOIMPORTS="goimports -w"; +find_files | xargs $GOFMT; +find_files | xargs $GOIMPORTS; \ No newline at end of file diff --git a/cmd/clef/4byte.json b/cmd/clef/4byte.json new file mode 100644 index 0000000000..5603d5931d --- /dev/null +++ b/cmd/clef/4byte.json @@ -0,0 +1 @@ +{"0x22ec1244": "shaBid(bytes32,address,uint256,bytes32)", "0xcae9ca51": "approveAndCall(address,uint256,bytes)", "0x4fb2e45d": "transferOwner(address)", "0x7741b4ec": "RandomNumberFromSeed(uint256)", "0x267127ec": "getTokenSettings()", "0xb7213bd4": "readLog(uint256)", "0x3018205f": "getController()", "0xc8edf65e": "GetAndReduceFeesByFraction(uint256)", "0xeec2b628": "beforeExecute(address)", "0xfc0c546a": "token()", "0x40a3d246": "toggle()", "0x70983e91": "startBoardProposal(uint256,address)", "0x6b5caec4": "setBot(address)", "0x78524b2e": "halveMinQuorum()", "0x2c60a055": "MapTest()", "0xc2fb8f36": "TinyHuman(address,address,address)", "0x6822abae": "getMinimumCallCost(uint256)", "0x6f9fb98a": "getContractBalance()", "0x5c17f9f4": "approve(address,uint256,bytes)", "0x504ac982": "transfer(string,string)", "0x06e53f47": "whichChainIsThis()", "0xf359671c": "withdrawWithReference(address,uint256,string)", "0xf97d0591": "parseTimestamp(uint256)", "0xd3c0715b": "vote(uint256,bool,string)", "0x6b069710": "scheduleCall(address,bytes,uint256,uint256,uint8)", "0x37ae43a3": "BetOnHashV81()", "0xab519020": "calcShare(uint256,uint256)", "0x6572ae13": "calculateWinner(uint256,uint256)", "0x6aaba012": "ErrorGenerator()", "0xfe05e8b1": "assertFact(uint256,string)", "0x6e940a29": "changeHost(address)", "0x669ee827": "RegisterDevice()", "0x6f4dd69c": "testSetBalanceUpdatesSupply()", "0x4401a6e4": "safeSend(address)", "0x27dc297e": "__callback(bytes32,string)", "0xe4dedc7f": "DeleteContract()", "0x7fef036e": "totalEntries()", "0x64325ddb": "currentClaimPrice()", "0x2fc0aad3": "isNumericString(string)", "0xbc45d789": "setConfigUint(int256,bytes32,uint256)", "0xee95feaf": "isSeller(address)", "0x358d5dc2": "getIsCashed(uint256,uint256)", "0x1397fdbd": "getShares(address,bytes,int256[])", "0x2d8c1c35": "level_up()", "0x24600fc3": "withdrawFunds()", "0x05f8b6f5": "_rewireIdentities(bytes32[],uint256,uint256,uint32)", "0x1840f0ca": "countVotes(uint256)", "0xd44aadf7": "initROS()", "0xca1d209d": "fund(uint256)", "0x5fa513d5": "findPtr(uint256,uint256,uint256,uint256)", "0x3c314a91": "playerGetPendingTxByAddress(address)", "0xd5582205": "getCertifiedStudentAtIndex(uint256)", "0xe45ebe93": "checkVoteStatus()", "0xcd9380d5": "testSetBalanceSetsSupplyCumulatively()", "0x637e86eb": "totBOTs()", "0x5bb47808": "setFactory(address)", "0x674cc1f5": "getMarketHashes(bytes32[])", "0x648bf774": "recover(address,address)", "0x0221038a": "payOut(address,uint256)", "0x4016535a": "parseBlock(bytes,uint256)", "0xa3908e1b": "convert(uint256)", "0xd9e7ee1c": "new_game(uint256,uint256)", "0x929e626e": "getShareDistribution(bytes32)", "0xa20495d3": "Managed()", "0xd409ddda": "EtherizationUtils()", "0xcb2b9031": "addressToBytes(address,address)", "0xfff3c457": "readMessages(uint256)", "0x043753ba": "makeDecision(uint256,bool)", "0x85b4bb53": "getSettings()", "0x60726abb": "copy()", "0xe50d0473": "SetRank(uint8,address,uint16)", "0x54ae8492": "CustodialForward()", "0xd6d02c51": "whois(string)", "0xcb712535": "_transferFrom(address,address,uint256)", "0xb152f19e": "getFirstSchedulableBlock()", "0x9334ab61": "Infos()", "0x88a49164": "testErrorUnauthorizedTransfer()", "0x17db59a4": "dEthereumlotteryNet(address,address,address)", "0xf85aefba": "testBitsSetFailIndexOOB()", "0xae99847b": "daylimit(uint256)", "0xd93e7573": "disown(bytes32)", "0xa5468081": "Pyramid(address)", "0x00e7d289": "registerListening(address)", "0x57ee24af": "getNum(bytes32,uint256)", "0xdaea85c5": "approve(address)", "0x36ffa905": "getMyProposals()", "0x7143059f": "getParticipant(address)", "0x55ff440a": "castStringToUInt(string)", "0x6a4a6b6e": "_myAddressHelper()", "0xb67fabdf": "scheduleTransaction(address,uint256,uint256,bytes)", "0xbcca1fd3": "changeVotingRules(uint256,uint256,int256)", "0x1d3390a1": "carefulSendWithFixedGas(address,uint256,uint256)", "0x45104b16": "EXECUTION_GAS_OVERHEAD()", "0xa26759cb": "addFunds()", "0x232523e8": "forceDivestOfAllInvestors()", "0x7e904a48": "getNumContents(uint256)", "0xb69c0896": "BaseScheduler(address,address,uint256)", "0xc6ed8e1b": "getApprovedProxys()", "0x4d1f8c31": "owner(uint64)", "0x17c65aa7": "getMaxLossAfterTrade(address,uint256,int256,int256)", "0x2c02d622": "precalculate()", "0xa035b1fe": "price()", "0x43b0e8df": "set(uint256,uint256,uint256)", "0x9b5fde7d": "payOut(uint256,string)", "0x89fcd099": "getApproval(address,address)", "0x4c0eceb5": "plusOnePonzi()", "0x880cdc31": "updateOwner(address)", "0xdab80d6f": "addSponsor(address)", "0x0fcda174": "getAccountTokenBalance(address,address)", "0xa55cab95": "getName(uint8,uint8)", "0x934db458": "Big()", "0xeb782d8c": "ContentSeries(address)", "0xdbfef710": "getDefaultRequiredGas()", "0x4f09eba7": "proxyApprove(address,uint256,bytes32)", "0xf4c5ab7c": "validateCallGas(uint256,uint256)", "0x376fe102": "userId(address)", "0x922dd59a": "icapTransfer(bytes,address,bytes,uint256)", "0x7318b453": "setVotetUntil(uint8)", "0xb8c86aa6": "getArraySettingResult()", "0x37bdc99b": "release(uint256)", "0x7cbcc254": "__reset__()", "0x37664643": "retractLatestRevision(bytes32)", "0x4b031d0f": "shortSellShares(bytes,uint8,uint256,uint256)", "0xad8d5f48": "exec(address,bytes,uint256)", "0x2f95b833": "requiredStackDepth()", "0xe3848e5b": "thing(string,string,string)", "0xaa272d4b": "getNodeIndexId(bytes)", "0xd7f746ce": "tickingBomb()", "0x3b84edbd": "setRNG(address)", "0x1fb2f2a0": "testUpdateLatestRevision()", "0xb7fba4d3": "getProxy(address)", "0x4b8e1ba8": "isMinter(int256,address)", "0xba4c206e": "removeCertificationDocumentInternal(address,bytes32)", "0x884b5dc2": "fill(uint256[])", "0x88017e05": "setContribution(uint256)", "0x1ff517ff": "totalDebt(address)", "0xd0315658": "getShareDistributionWithTimestamp(bytes)", "0x7d03f5f3": "newGame()", "0xb7538f3e": "ChangeClient(address)", "0xbf4d89b5": "parseInt(string,uint256)", "0x7b55c8b5": "scheduleCall(address,bytes4,bytes,uint8,uint256[4])", "0x350d141e": "getWasApprovedBeforeDeadline()", "0x27960c5f": "validateEndowment(uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0xb774d3d7": "BankOwner_GetDonationsBalance()", "0x267b6922": "entries(bytes32)", "0x08b7c13b": "getExists(bytes20)", "0x7e7a2fbf": "contribute_toTheGame()", "0x5b86914d": "bet_value()", "0x0e1087c3": "getMarketMakerFunds()", "0xf7149220": "RNG()", "0x345006b6": "getGenerationForCall(address)", "0xc4b14e0b": "getSignature(bytes32)", "0x419945f8": "ExpiringMarket(uint256)", "0x41868769": "CallAborted(address,bytes)", "0x29092d0e": "remove(address)", "0x746c9171": "m_required()", "0x5020dcf4": "convertToEach(uint256,string,uint256)", "0xa06db7dc": "gracePeriod()", "0xbf8fc670": "sendToAggregation(uint256)", "0xf14fcbc8": "commit(bytes32)", "0xa538d287": "getMinMax()", "0xcae523c1": "testOwnedTryAuthUnauthorized()", "0x04d10f1c": "isValidChainyJson(string)", "0x9ba5b4e9": "getEventHashes(bytes32[])", "0xfedfd535": "Config()", "0x42ce0f30": "testThrowUpdateLatestRevisionNotOwner()", "0x31be6985": "testBitXorSuccess()", "0x173cb7de": "getNumReleasesForNameHash(bytes32)", "0xd90a88cd": "getContentReplies(uint256,uint256)", "0x92eefe9b": "setController(address)", "0x052b81c7": "releaseBadges()", "0xb2855b4f": "setFeeAddr(address)", "0x19a9c2f1": "generateId(string)", "0xfa9acb05": "addressInArray(address,address)", "0x3da5c3ce": "puzzle(address,bytes32)", "0x7a427d98": "forceReturn()", "0x70e71ea3": "etherandomSeedWithGasLimit(uint256)", "0xd7a58658": "changeHouseedge(uint8)", "0x72b75585": "getOriginalClient()", "0xf802075f": "requiredEndowment()", "0x7997b997": "doMelt(uint256,uint256)", "0x6d5433e6": "max(uint256,uint256)", "0xb651cbaf": "add_level(address,bytes)", "0xb4d6d4c7": "getPackageData(bytes32)", "0x90e3c278": "getShares(uint256[128])", "0x179b73da": "killBoardProposal(uint256,address)", "0xc944a38e": "CharlyLifeLog(string,int256)", "0xe1c66292": "Create(uint32,address)", "0x69c8b344": "ownedToken(address)", "0xabcf1328": "InterestBank()", "0x532e7e6a": "calcEarningsSelling(bytes,uint256,uint256[],uint8,uint256)", "0x43d24a5e": "addUpdater(address)", "0xd1feca67": "addSpendingRequest(address)", "0x2d34ba79": "setup(address,address)", "0xcb14d93b": "getHash(bytes,address,uint256)", "0x309424fe": "get_all_names()", "0x96c52fc3": "____forward(address,uint256,uint256,bytes)", "0xde39acea": "get32(bytes,uint256)", "0xf3dd3d8a": "newCurrency(string,string,uint8)", "0x2432eb23": "testThrowRetractLatestRevisionNotUpdatable()", "0x7fcf3a2f": "throwFooBar()", "0xabe9f569": "oraclize_getPrice(string,uint256)", "0x41ee903e": "clear(uint256,uint256)", "0xd249a52e": "update(bytes,uint256[],uint256[])", "0xc3d014d6": "setContent(bytes32,bytes32)", "0x3ac5cb73": "GeometricPonzi()", "0x4a1aa767": "claim_victory(uint256,uint8,uint8,uint8)", "0xce592586": "setThresold(uint256,uint256)", "0x63deb2c5": "changeMemberAddress(address)", "0x2e6e504a": "trusteeWithdraw()", "0xcfed9199": "timePassed(uint256)", "0xb782fc9b": "getFirstActiveDuel2()", "0x35b28153": "addAuthorization(address)", "0x46f7a883": "BuyTicket(uint8,uint8,uint8)", "0x83c51a38": "thesimplegame()", "0xfa28ba0d": "validateReleaseLockfileURI(string)", "0xa7b2d4cb": "remove(int256,address)", "0x010731c0": "sendCryptedHand(bytes32)", "0xe9e99d81": "getChannelFeed(address,uint256,uint256,uint256)", "0x4e30a66c": "safeToAdd(uint256,uint256)", "0x2c4e591b": "totalGames()", "0xa3221c8e": "step8()", "0x783ce458": "expmod(uint256,uint256,uint256)", "0xe417291b": "undelegateDAOTokens(uint256)", "0x8e5d97a2": "releasePendingTransfer(uint256)", "0xbc5ff5e1": "oraclize_query(string,string[4],uint256)", "0x38f77d69": "getDistributeProfitsInfo()", "0xbb510a77": "createChannel(address,uint256)", "0x650955d4": "HashToken()", "0xa8484938": "doApprove(address,uint256)", "0x64ed31fe": "authVotes(address)", "0xf7ae9421": "checkInvestorBalance(address)", "0xba904eed": "removeRegistrar(address)", "0xdce4a447": "at(address)", "0xdb4cacec": "Other()", "0x3647b87a": "buildFactory()", "0xa51aea2d": "changeMaxMultiplier(uint256)", "0x4974bc27": "download()", "0xf8a8fd6d": "test()", "0xd8c90762": "addTrustedIssuer(address,string)", "0xdf6c13c3": "getMinFunding()", "0x867904b4": "issue(address,uint256)", "0x1531c267": "fipsRegisterMulti(uint256,address,bytes)", "0x40a49a96": "searchSmallestInvestor()", "0x61bffe01": "addIdentities(bytes32[],bytes32[])", "0xf77a0923": "BitcoinProcessor(address)", "0xd02528e6": "GetGameIndexesToProcess()", "0x9f927be7": "getNextCall(uint256)", "0xd8162db7": "lockedUntilBlock()", "0x36dfe260": "payOneTimeReward()", "0xc5b1a53c": "deposit(bytes16[],uint64)", "0xc2b6b58c": "isClosed()", "0xc88cc6ac": "getCertification(address)", "0x77ac3da5": "oraclize_query(uint256,string,string[1],uint256)", "0x70ab8ba8": "creditUpdate()", "0xd3ea3322": "testBuildTokenSystemCost()", "0x72388610": "paybackAll()", "0xca6d56dc": "addMember(address)", "0x0994a0a0": "DSTokenTest()", "0xe53e04a5": "refillGas()", "0xc1d5e84f": "addNewUser(address)", "0x89ed0b30": "setOraclizeGas(uint32)", "0x02ba8742": "sendCoins(address,uint256)", "0xb0de1cb7": "publish(uint64,bytes,uint64)", "0x0e13b9af": "getValue(uint8,uint8)", "0xb3dfcdc3": "Contribution(uint256)", "0xa9b35240": "packageExists(bytes32)", "0xd1d3bb92": "testSetPkg()", "0x97297467": "checkAndVerify(bytes)", "0xe31bfa00": "next_id()", "0x9948e493": "calcMarketFee(bytes,uint256)", "0xd148288f": "setHoldingPeriod(uint256)", "0xc032dc30": "execute(uint256,address)", "0xdad99989": "burnCoins(address)", "0xb1999937": "leapYearsBefore(uint256)", "0xa6cbcdd5": "numSignatures(bytes4)", "0xaca66aec": "DVIP()", "0x20bf0c52": "Derived(uint256)", "0x693ec85e": "get(string)", "0x0411bca8": "getChallengeAnswerResult(uint256)", "0x61584936": "sealedBids(bytes32)", "0x2f1927cb": "prepareRoll(uint256,uint256,uint256)", "0xeaa1f9fe": "reqisterListening(address)", "0xb623f5e5": "checkSetCosignerAddress(address)", "0xa88c5ef7": "NextPayout()", "0x66ad484c": "newfirst_player(address)", "0xb4022950": "collectFeesInEther(uint256)", "0xbff0fbb8": "calculateMeat(uint256)", "0xd62b255b": "setOwner(address,string)", "0x2fd6d40b": "getBetValueByGamble(uint8)", "0x3b0506f7": "getVoteByAddress(address,uint256)", "0xbddd3a6b": "step7()", "0x67fbd289": "destroyTokens(uint256)", "0x9844347b": "createCertificate(bytes,bytes,uint256,bytes)", "0x5e68ac2c": "Kingdom(string,address,address,address,uint256,uint256,uint256,uint256,uint256)", "0x8ba9f354": "testClearBitSuccess()", "0x48027610": "transferPaidOut(address,address,uint256)", "0x912de8de": "fixBalance()", "0x04509918": "scheduleCall(address)", "0x7cf0ffcb": "forceDivestAll()", "0x3b3b57de": "addr(bytes32)", "0xeb7c6f72": "step6()", "0xfe6f0d82": "testConstructorEvent()", "0x55b62dcf": "getThresold(uint256)", "0xfbae5e7d": "Investors(uint256)", "0x29e206bd": "forceDivestAll(bool)", "0x6a226a49": "addMessage(string)", "0x8e2a6470": "allocateShares(address,uint256)", "0xe6e7237f": "claim_time_victory(uint256)", "0x17a601b5": "MAX_STACK_DEPTH_REQUIREMENT()", "0x87fd0421": "TheEthereumLottery()", "0xc17e6817": "sendSafe(address,uint256)", "0xa5dfee67": "testThrowsCreateNewRevisionNotUpdatable()", "0xb35893f3": "setExporter()", "0x1ceea715": "GetMyInvestFee()", "0xb78bd4a5": "breakCookie(string)", "0x05215b2f": "createStandardToken(uint256)", "0x2632bf20": "unblockMe()", "0x5292af1f": "sendBalance(address)", "0xc2e9fab3": "SubUser()", "0x6493d7fc": "CircuitBreaker(address,address,uint256,uint256)", "0x4f896d4f": "resolve(uint256)", "0x16870257": "getTileDescription(uint8,uint8)", "0x3ef87414": "getRevisionCount(bytes20)", "0x747586b8": "setInt(int256)", "0x5714f6a1": "getTotalAvailableRelays()", "0x99154b49": "ARK()", "0x1efb17ee": "changeHouseAddress(address)", "0x354d7e40": "Payout()", "0x2da0d1ea": "etherSold()", "0xea46193e": "getEtherBalance()", "0x11fe773d": "memcpy(uint256,uint256,uint256)", "0x1e701780": "MICRODAO(address,uint256,uint256,uint256,address)", "0x1c31f710": "setBeneficiary(address)", "0x0a4caed0": "getChannelByRank(address,uint256)", "0xf3125a1f": "deposit(address,uint256,bytes,uint256)", "0x00e46700": "setMinimumPassPercentage(uint8)", "0x92d282c1": "Send()", "0x89d59ee5": "createPersonalDepositAddress()", "0xbe1c766b": "getLength()", "0x70a08231": "balanceOf(address)", "0xae0a6b28": "signature(string,bytes32)", "0xb3485dca": "UpdatePrice(uint8,uint32)", "0xf8ec4bf2": "setAllowTransactions(bool)", "0x53d97e65": "setPrizes(uint32[])", "0xd1b1a22b": "set(string,uint256[],uint256[],uint256[],bool[],uint256[])", "0x96286cc9": "isTokenOwner(address)", "0x154af6b1": "sendShares(uint256,uint8,uint256,address)", "0xbe2430fe": "sendValues()", "0x57a373a1": "uintInArray(uint256,uint256,int256,uint256[],uint256)", "0x8fd28bcf": "testFailAuthorityAuth()", "0x89ef40e7": "numberOfHealthyGenerations()", "0x23e9c216": "setBounty(address,string,uint256)", "0x71dd99fe": "BigRisk()", "0x1e9fcc77": "activateAllowance(address,address)", "0x561e91a1": "makeBet()", "0x32d2fb9f": "getRefRemainingTime(uint256)", "0x992c870d": "transferName(bytes,address)", "0x6b3fdc5a": "oraclize_setNetwork(uint8)", "0x2ea459b8": "claimThrone(bytes)", "0x33a99e04": "selectWinner()", "0x3b49a77b": "hasConfirmed(bytes,address)", "0xa352f1a8": "calcSHA3(bytes)", "0x4bb4b260": "cashAllOut()", "0xb89a73cb": "isShareholder(address)", "0xba5a2d33": "exitPool(address)", "0xddd41ef6": "transferDirector(address)", "0xa06cab79": "Registrar(address,bytes32)", "0x871113c3": "oraclize_query(string,string[1],uint256)", "0x1f0f711f": "discontinue()", "0x632f0ba6": "descriptionHashes(bytes)", "0x980e8c81": "FutureBlockCall(address,uint256,uint8,address,bytes,uint256,uint256,uint256)", "0x4ae8c55f": "getWwLength()", "0x82fc49b8": "setCosignerAddress(address)", "0xc4bd8ebc": "num_monsters()", "0x0381cb3b": "setRowcol(uint256,uint256[2])", "0x124eaee6": "Identity()", "0x3f4be889": "callContractAddress()", "0xef3a6031": "testBaseToken()", "0x954ab4b2": "say()", "0x1b855044": "getHash(uint256,uint256)", "0xd9d73887": "Diana()", "0x5103a5a3": "certify(address,bytes32)", "0x51560da9": "topDogInfo()", "0xf3ee6305": "removeCertificationDocument(address,bytes32)", "0x049ae734": "scheduleCall(address,bytes4,uint256,uint256,uint8)", "0xd8a8e03a": "move(uint256,address)", "0xc3c5a547": "isRegistered(address)", "0x06005754": "nameRegAddress()", "0xbe592488": "validateName(bytes)", "0x0eecae21": "draw()", "0xac3e7d24": "addChainyData(string)", "0xfd83f3e3": "QueueUserMayBeDeliveryDroneCotnrol()", "0x7772a380": "isInGeneration(address,uint256)", "0xeb1ff845": "changeId(uint256,uint256,uint256)", "0x9cc9299e": "killSwap()", "0x1e2ca0f3": "updateLeftLottery(address)", "0x998446a8": "acceptRequest(uint256,bytes)", "0x8e1ffb19": "testThrowsRetractLatestRevisionEnforceRevisions()", "0x9935935f": "setResolveHandler(bytes,address)", "0xcd4b6914": "getRandom(uint256)", "0xc08dd1dc": "IOU(string,string,uint8)", "0xbe4054b9": "commitReading(address,uint256,uint256,string)", "0xbc21ce9d": "Aggregation()", "0x6e173a7f": "storeBlockHeader(bytes,bytes)", "0x114d69b2": "setCRLaddr(address)", "0x3fa4f245": "value()", "0x69573648": "remove(bytes,bytes)", "0x7fee4ecb": "GAS_PER_DEPTH()", "0x591c515f": "append(string,string)", "0x727b1cd6": "next_draw(bytes32,uint256,uint256,uint256,uint256,uint256)", "0xc60ce271": "findNextMinute(uint256,bytes)", "0xd337616e": "resetLottery()", "0xdacaeb07": "pledge(bool,uint256)", "0xb29c2493": "token(uint256,string,uint8,string)", "0x61047ff4": "fibonacci(uint256)", "0x8f367001": "numTokensAbleToPurchase()", "0x12cc08f2": "getPackageReleaseHashes(string,uint256,uint256)", "0x67a59d91": "scheduleCall(address,bytes,bytes,uint256,uint256,uint8)", "0xe6c3b4ab": "testBalanceAuth()", "0xd526b9bd": "_allow()", "0x29de91db": "setMsg(address,uint256)", "0xd1cf113e": "multiAccessSetRecipient(address)", "0xc2412676": "Token()", "0x391f2e96": "InvestCancel()", "0xc0ae6a3a": "ultimateOutcomes(bytes)", "0x202d6eaf": "addInvestorsValue(uint256)", "0x30b9af98": "withdrawFunding()", "0xe80bd3e5": "addCertificationDocumentToSelf(bytes32)", "0xf4e36afd": "findThroneByNameHash(uint256)", "0x30677b83": "multiplierFactor()", "0x590528a9": "sellShares(uint256,uint8,uint256,uint256)", "0x01cceb38": "setExpiry(uint256)", "0x779beca0": "getNumOfSalesWithSameId(bytes)", "0xac940823": "betOnLowHigh(bool)", "0x06961560": "DAO(address,uint256,uint256,uint256,address)", "0xd42bf301": "doTriggerTryAuth()", "0xfa566ddd": "doAllowance(address,address)", "0x6677febe": "get_all_accepted()", "0xaa67c919": "depositFor(address)", "0xf28386bf": "Nexium()", "0x77e4fb04": "testFailNotEnoughValue()", "0x12b58349": "getTotalBalance()", "0xc0d2834b": "DataSource()", "0x3e82055a": "addSignature(uint256,bytes16,bytes)", "0xcff2fa42": "_returnFee(address,uint256)", "0xa056469a": "extractFeeLength()", "0xc98031be": "hintURL(int256,bytes32,string)", "0x6ebbe863": "updatePublishContract(address)", "0x08216c0f": "createHumanStandardToken(uint256,string,uint8,string)", "0xc36af460": "getLatest()", "0xdb5b4183": "oracleOutcomes(bytes,address)", "0x0b5ab3d5": "destroyDeed()", "0xe1c7392a": "init()", "0x4ca1fad8": "addRequest(uint256)", "0x305b73d9": "configure(address,address,uint256,uint8,bytes32,bytes32)", "0x9077dcfd": "submitCoding(string,uint256)", "0x38fff2d0": "getPoolId()", "0x07bc6fad": "withdraw(address,uint256,bytes32,uint256)", "0xfbf58b3e": "transfer(string,address)", "0x1d8b70da": "order_received(string)", "0x0b3ed536": "claimDonations(uint256)", "0x6f374a12": "setBool()", "0x0ca35682": "recover(uint256)", "0x3ae7cdfa": "fipsLegacyRegister(bytes20[],address)", "0xe6c1beb4": "prepend(address)", "0x776d62f6": "costs()", "0xe4690a0b": "popRequest()", "0x74eb9b68": "isAccountLocked(address)", "0x7d32e7bd": "transfer(address,bytes32)", "0xdf2f0a4a": "getDecisionBlockNumber(uint256,uint256)", "0xc494f71a": "LedgerFund(uint32,uint32,uint64,uint64)", "0x446d5aa4": "getAttributes(address)", "0x4cdc6a73": "Marriage()", "0x677cee54": "SafeConditionalHFTransfer()", "0x7b48ba20": "testThrowDisownNotOwner()", "0x1288c42a": "Prism()", "0xe8b13c44": "getChainyTimestamp(string)", "0xe4c2db06": "getPreviousFile(bytes)", "0xf0586f0d": "doThrow(bool)", "0xc1b06513": "registerEvent(bytes32[])", "0x521eb273": "wallet()", "0x32254992": "getPrevHash(int256)", "0x1fd96b69": "ManagedAccount(address,bool)", "0xabf74a93": "pitFee()", "0xa480ca79": "collectFees(address)", "0xa0bde7e8": "getShareDistributionWithTimestamp(bytes32)", "0xff27c476": "shiftBitsRight(bytes,uint256)", "0x172d8a30": "setDirectorLock(uint256,uint256)", "0xf262de8c": "add_staircase(uint16)", "0x990f3f53": "computeResponseSecondHalf(uint256,uint16)", "0x26745909": "PRNG_Challenge()", "0xcacc24eb": "transferFromViaProxy(address,address,address,uint256)", "0x94f3f81d": "removeAuthorization(address)", "0x3f0ec70b": "RequestFactory(address)", "0xa2a8336f": "claimEtherSigner(uint256)", "0xaa5d4719": "getTransferable(bytes20)", "0x23cd7cd5": "Model()", "0x3fb0b2c9": "CancelRoundAndRefundAll()", "0xd5fa2b00": "setAddr(bytes32,address)", "0xa0f61310": "FakeRelay(bytes)", "0x4ea66c38": "buyinInternal(address,uint256)", "0xbe040fb0": "redeem()", "0xb845c9a2": "WEI()", "0x26a7985a": "getMaximumCallGas()", "0x06661abd": "count()", "0xc89f8f08": "testGetController()", "0x81baf820": "BlockScheduler(address)", "0x9801cb8e": "ProofOfExistence()", "0xeb7492d1": "testTotalSupply()", "0x3dfb4843": "renewDeed(bytes32)", "0xc3fa5f93": "BlockScheduler(address,address)", "0x7958533a": "meta(uint256,bytes32)", "0xa1a66e56": "deductFunds(uint256)", "0xaf92a693": "addRegistrar(address)", "0xb2aac51f": "lookupUser(string)", "0xd70cf105": "moveBalance(address,address,uint256)", "0x2afb21bc": "InvestWithdraw()", "0x6d09e2ec": "commitCurrency(address,uint256,uint256)", "0x7b1a4909": "transferETH(address,uint256)", "0x96c824a8": "createAccountFundContract()", "0xe0a70811": "restart(bytes20,bytes)", "0x22057bc7": "getAllRevisionBlockNumbers(bytes20)", "0x6af2da2f": "testKeyedHash()", "0x7f6d8955": "RegisterOne(uint32,address,address)", "0x65f27bea": "testSubBalanceFailsBelowZero()", "0xa2f16d80": "dexWithdrawCollectedFees()", "0xc179520c": "ManageAccount()", "0x2672b3e2": "SplitterEtcToEth()", "0xe839e65e": "query2(string,string,string)", "0x39f64b52": "calcTokenPrice()", "0x4ef5710a": "WatchNumberOfPlayerInCurrentRound()", "0x3017fe24": "callAPIVersion()", "0x2977b1b1": "testAllowanceStartsAtZero()", "0x531c1b33": "getOperatingBudget()", "0xb7f2f33c": "transferRightIfApproved(address,bytes)", "0x00873367": "comparisonchr(string)", "0x2a0d79ef": "totalSupply(bytes)", "0xa715ff59": "EtherandomProxy()", "0xd6ca8ccb": "disown(bytes20)", "0x6ad2a0b3": "buildContract(address)", "0x45596e2e": "setFeeRate(uint256)", "0x0e97cfdf": "placeOrder(uint256,uint256,uint256)", "0x9549355e": "oracalizeReading(uint256)", "0x8d7af473": "numberOfProposals()", "0x728af7ec": "getInterest(uint256,uint256)", "0x11b9fee8": "ForkChecker(uint256,bytes32)", "0xd850288b": "etherlist_top()", "0xf4dc2d21": "Deed(uint256)", "0xf8b11853": "getGenerationStartAt(uint256)", "0x7c7a52bf": "newChallenge(uint256,address)", "0xd2d4bd72": "getCrossRate(bytes,bytes)", "0xe9b93569": "OwnerGetFee()", "0xfb72d24e": "shift_right(uint64,uint256)", "0x112e39a8": "scheduleCall(uint256)", "0x6c494843": "multiAccessChangeOwnerD(address,address,address)", "0x313ce567": "decimals()", "0x9bac8602": "testFailAddBalanceAboveOverflow()", "0xa70a9ad7": "switchDeity(address)", "0x6a61e5fc": "setTokenPrice(uint256)", "0x990c8f79": "returnValue()", "0xa4136862": "setGreeting(string)", "0x0af4626d": "testRetract()", "0x5e11544b": "newPeriod()", "0xdc206e5f": "oraclize_query(uint256,string,string[])", "0xcaa648b4": "getTotalValue()", "0x20bfec70": "WatchFees()", "0x62a0b56b": "testUnset()", "0x42f6e389": "isModule(address)", "0x769796fe": "resetAction(uint256)", "0x402e6230": "getTotalGambles()", "0xe8a1c08f": "nibbleToChar(uint256)", "0x1aa3a008": "register()", "0x96d02099": "rsplit()", "0x83324e8c": "numGroups()", "0x72c7c85a": "minority()", "0xb8d94b95": "buildDSNullMap()", "0xe039e4a1": "getOwner(uint8,uint8)", "0x625cc465": "baseDonation()", "0x77372213": "setName(bytes32,string)", "0xa7dfc874": "unregister(bytes,address,uint256,bytes)", "0x37f4c00e": "anchorGasPrice()", "0xb2bfd948": "checkNumbers(uint8[3])", "0x512f1e64": "orderBookLength()", "0xafed762b": "toSlice(string)", "0xbb6a1427": "testThrowRestartEnforceRevisions()", "0x734d8287": "unclaimedFees()", "0xf295206f": "_unsafeSend(address,uint256)", "0x69d01268": "concatUInt(uint256)", "0x0494630f": "oraclize_query(uint256,string,string[4],uint256)", "0x13fc6ac2": "getEventData(bytes32)", "0xbff974e8": "getContentReplies(uint256)", "0x18921de4": "addSignature(string,uint256[],uint256[],uint256[],bool[],uint256[])", "0xa87e7552": "isValid(bytes,bytes)", "0xb8d3bfe3": "MeatGrindersAssociation(address,address,uint256,uint256,uint256,address)", "0x61461954": "execute()", "0xecb0256b": "relayTx(bytes,int256,int256[],int256,int256,bytes,int256,int256[],int256,int256)", "0x7cdbae63": "addRegistryIntoTagsIndex(address)", "0x1f4e996b": "challenge(bool)", "0x0eb0afa6": "createDebt(address,address,uint256)", "0x5f6a1301": "clearPending()", "0x305a762a": "getTicketsCountByBuyer(uint256,address)", "0x724ae9d0": "getMinInvestment()", "0x1e39499d": "scheduleCall(address,bytes,uint256)", "0x4f197ee7": "transferPackageOwner(string,address)", "0x7e3faec1": "GoldTxFeePool(address,address,bytes)", "0x8d68cf59": "sendFunds()", "0x83eed3d5": "queryN(uint256,string,bytes)", "0x15c91115": "pbkdf2(bytes,bytes,uint256)", "0xeb121e2f": "update(uint256,uint256[101][])", "0x5e44daf3": "vote(uint256,int256)", "0xac562666": "freezeCoin()", "0xb0166b04": "testTransferringMkr()", "0x631de4d6": "replace(address,address)", "0x4bd70ea3": "testFailGetUnset()", "0xf738e5ca": "ownerTakeProfit()", "0xc6236a5c": "scheduleCall(bytes,uint256,uint256,uint8,uint256)", "0x119aa5c8": "checkForward(bytes)", "0x541aea0f": "put(uint256,uint256)", "0x6386c1c7": "getUserInfo(address)", "0x4e209678": "testFailBreach()", "0xe9fe799e": "registrantRemove(address)", "0x2aee19c7": "testCreateWithNonce()", "0xa0ec4e09": "getUltimateOutcomes(bytes32[])", "0x4d9e4e22": "Etheria()", "0xa6b513ee": "finalPrice()", "0x82f0d875": "makeHash()", "0x78ae88d1": "newDeal(uint256,uint256,uint256,uint256,uint256)", "0x177766e6": "getOptionChain(uint256)", "0xf1173928": "RemovedFromGeneration(address,uint256)", "0xea2ea847": "testChallengeFinalize()", "0xbd35d570": "GAS_TO_COMPLETE_EXECUTION()", "0x364ea9e7": "set(uint256,uint256,bool[],uint256[])", "0x17ff0caa": "WeatherBet(uint256,address,address,address)", "0x4e23a144": "fundUser(address,uint256)", "0x144267e0": "refundSecurity(address,uint256,uint256)", "0x31c6c4cf": "transferFromWithReference(address,address,uint256,bytes32,string)", "0x2eb5c61f": "testThrowsUpdateLatestRevisionEnforceRevisions()", "0xde640e19": "Investment(uint256)", "0x9cb8a26a": "selfDestruct()", "0x9c43d950": "registration(uint256,uint256,uint256)", "0xe2fdcc17": "escrow()", "0xc618d15f": "ConvertNumbers(bytes5)", "0x8c98117c": "getBill(uint256,uint256)", "0x2d7788db": "rejectRequest(uint256)", "0xfaab9d39": "setRegistrar(address)", "0xa289673b": "fipsChangeOwner(bytes20,address,address)", "0x54d9d6f8": "findNextDay(uint256,bytes)", "0x008a745d": "claimDividendShare(uint256)", "0x6f4812e2": "testFailControllerInsufficientFundsTransfer()", "0x5e983d08": "setPrices()", "0x798974dd": "getNumProposals()", "0x4ca168cf": "register(bytes,uint256,address,string,uint256)", "0xa1e4d3c2": "MembershipRoster()", "0xd4065763": "returnRemainingMoney()", "0x0c4326a0": "getMajorMinorPatch(bytes32)", "0xeece1e1f": "scheduleShuffling()", "0x226685ee": "Visit()", "0x323082d7": "Vote(string)", "0x0b15650b": "randInt(uint256,uint256)", "0xc9cfac55": "refundCurrency(address,uint256,uint256)", "0xfe4a3ac9": "setExecPrice(uint256)", "0x6a0e605f": "MyToken(uint256,string,uint8,string,address)", "0xb549793d": "scheduleCall(bytes4,bytes,uint256,uint256,uint8,uint256)", "0x85e68531": "revokeAccess(address)", "0x01991313": "scheduleCall(address,bytes4,uint256)", "0x0a6be0e7": "BalancedPonzi()", "0xf463edd1": "createDocument(uint256)", "0xa20c404f": "ModifySettings(uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x560bb612": "SignatureValidator(address)", "0xf7654176": "split()", "0x48f05187": "scheduleCall(address,bytes4,bytes,uint256)", "0xf2b904c3": "checkBetColumn(uint8,address,bytes32,bytes32)", "0x7bc0ff20": "setupExportFee(address,uint256)", "0xeb06e65e": "allowanceFromProxy(address,address,address)", "0xfe757fb5": "lastClaimPrice()", "0xa5d0bab1": "buyPartial(uint256,uint256)", "0xda7d0082": "isCertification(address,bytes32)", "0xe570be18": "DVIPBackend(address,address)", "0x54738157": "OwnerCloseContract()", "0xc1e5304a": "CreateNewDraw(uint256,bytes)", "0x0c26e42e": "getReleaseHashForNameHash(bytes32,uint256)", "0x0f7d6673": "Channel()", "0x54ea4000": "identify(address[])", "0x69307c80": "rotateBits(bytes,int256)", "0x78f0161a": "setGreyGreenPrice(uint8)", "0x23b872dd": "transferFrom(address,address,uint256)", "0x578bcc20": "reduceDebt(address,address,uint256)", "0x59e148fc": "getLastOfferId()", "0xb5299ca6": "giveMeat()", "0xae30d35d": "ARK_TROGLOg_1_00()", "0x2d2c44f2": "Vault()", "0xce19419b": "testThrowsSetNotUpdatableNotOwner()", "0xffcf21a9": "eventOracles(bytes,uint256)", "0xf46c50dc": "doFail()", "0x73b55eaf": "registerData(address,int256,bytes32,address)", "0x53770f9a": "isStateless()", "0x4d47feaa": "ShareholderDB(uint256)", "0x40b31937": "pledgeDecline(uint256)", "0x01cb3b20": "checkGoalReached()", "0x62e05175": "setMotionDB(address)", "0xf362d78f": "testBitNotEqualSuccess()", "0xd2531590": "CANCEL_EXTRA_GAS()", "0x9a92b7e7": "EthVenturesFinal()", "0x79b0797c": "AmIPlayer1()", "0x6241bfd1": "Token(uint256)", "0x94a1710d": "testNonOwnerCantBreach()", "0xb466b76f": "fresh()", "0x4a5dddd2": "proxyPurchase(address)", "0xc0a1a949": "x15()", "0xc3b8bfe5": "transferIfNoHF(address)", "0x4a7e049e": "getFullCompany(address,uint256)", "0x481b659d": "permitPermanentApproval(address)", "0x16ce8a69": "setBuilding(uint256,uint256)", "0x1593a8c7": "endLottery()", "0x078c3fa4": "_transferToICAPWithReference(bytes32,uint256,string)", "0xfa3f1e99": "testBlobStoreRegistered()", "0x0b9e9817": "CanaryV7FastTestnet()", "0x6663bbec": "orderMatch(uint256,uint256,int256,uint256,uint256,address,uint8,bytes,bytes,int256)", "0x273bc3c9": "numberOfThrones()", "0x3c84f868": "set(int256,address,uint256)", "0x1ac61e8c": "testBlobCreate()", "0x5ccc3eaa": "roundMoneyUpToWholeFinney(uint256)", "0x0ccec396": "getNumReleases()", "0x6ac6205c": "addDataPoint(int256,uint256,bool,string)", "0x1d124fe4": "setUtils2(address)", "0x4c471cde": "scheduleCall(address,bytes4,bytes,uint256,uint256,uint8,uint256)", "0x52a554a1": "voteBoardProposal(uint256,address,bool)", "0x745a8be2": "flip32(bytes)", "0xbac1e2e0": "testBitsAndSuccess()", "0x25fda176": "notify(address,uint256)", "0x3b8e6f2e": "balanceAt(address,uint256)", "0x60585358": "getByte()", "0xc853c03d": "newDraw(uint256,uint8[3],uint256,uint256,uint256,uint256)", "0x741273d6": "testThrowRegisterContractAgain()", "0x8f2c44a2": "UnicornMilker()", "0x59d96db5": "terminate(uint256,string)", "0x483ba09e": "setBitcoinBridge(address)", "0x74fbbc86": "rate(uint256,uint256,string)", "0x83ea0620": "packageExists(string)", "0xd917deb5": "Donate()", "0x3fc6bc94": "payDAO()", "0x6558488a": "scheduleSetBool(address,uint256,bool)", "0x83e78b31": "bet(uint8,bool,uint8)", "0xeccb15bc": "SatPosition(int256,int256)", "0x7daa10ce": "getMyInfo()", "0x3e4565d2": "testErrorUnauthorizedNameRegister2()", "0x2143da91": "GameOfThrones()", "0xb29f0835": "doIt()", "0xdcc0ccf3": "Dao(address)", "0x70d53be5": "find()", "0x9a828a71": "oracalizeReading(uint256,string)", "0x6a6d31db": "externalEnter()", "0xf8b71c64": "rewardTo(address,uint256)", "0x0399c357": "assignFreeReadings(address,uint8)", "0x81ade307": "query(string,string)", "0xdb83694c": "getSaleInfo()", "0xa6bf3df0": "oraclize_query(string,string[2],uint256)", "0x29605e77": "transferOperator(address)", "0xf29d2f28": "setTokenHolder(address)", "0xa96f8668": "releaseTokens()", "0x8a3bc2bc": "iPropose(bytes,uint256,bool)", "0xd18611d6": "reactivate()", "0x7620a65b": "Publisher()", "0xa268b332": "testBitXorFailIndexOOB()", "0x6b1781b6": "Emergency()", "0x1003e2d2": "add(uint256)", "0x1209b1f6": "ticketPrice()", "0xe5a27038": "Pluton(uint256,string,uint8,string)", "0x22bc3b8e": "getArgument(uint256)", "0x47bdb7f4": "transferDisable(bytes20)", "0x13137731": "testThrowsUpdateLatestRevisionNotUpdatable()", "0x3f3935d1": "confirmReverse(string)", "0xecb4136e": "NotAnotherPonzi()", "0x49e347ae": "getContents(uint256[],uint256)", "0x669dafe8": "toWei(uint256)", "0xc233e870": "isLatestPatchTree(bytes32,bytes32)", "0x7b789b3d": "agreement(bytes,bytes,bytes)", "0x682d3bb0": "pdfCertificateProof(bytes)", "0x42346c5e": "parseInt(string)", "0x3177029f": "approveAndCall(address,uint256)", "0x71ffcb16": "changeFeeAccount(address)", "0xc971442c": "getDBs()", "0x362e2565": "returnDeposits()", "0xe10e274a": "CrazyEarning()", "0x6d705ebb": "register(address,uint256)", "0xbe9a6555": "start()", "0x1ce624d6": "Crypted_RPS()", "0x2c4cb4be": "removeRegistryFromNameIndex(address)", "0x68742da6": "withdrawFunds(address)", "0x18f3fae1": "setOversight(address)", "0x061ea8cc": "countByOwner(address)", "0xd6d22fa4": "MetaCoin()", "0x85654c9c": "setMembershipRoster(address)", "0x8aa33776": "setMsgPrice(uint256)", "0x4dd850fb": "UfoPonzi()", "0x07e00bcb": "kissBTCCallback(uint256,uint256)", "0xa1b7ae62": "setdirectorName(string)", "0xb4d9cc3a": "profitDisperser()", "0x0f24f5c8": "doTransfer(address,uint256)", "0x8d72a473": "deductFunds(address,uint256)", "0x28f03554": "ProcessDividend()", "0x98391c94": "muteMe(bool)", "0x346cabbc": "scheduleCall(address,bytes4,uint256,bytes,uint256)", "0xa42e36c6": "scheduleTransaction(address,bytes,uint8,uint256[5],uint256)", "0x21b36a08": "setFee(uint64,uint256)", "0xd94073d4": "PT()", "0xe8580dd4": "Survey(address,uint256,string,bytes32[])", "0x1f0c1e0c": "getEventTokenAddress(bytes32,uint256)", "0xce8b7151": "isHF()", "0x9bee757b": "requestExecution(bytes,uint256)", "0x775dec49": "keccak()", "0x6673ce2b": "Results_of_the_last_round()", "0x9f87acd0": "exec(bytes32,bytes32,uint256)", "0x02394872": "getLastBlockHeight()", "0x615664ba": "Market()", "0x0d7af726": "addGame(address,string,string)", "0xf4aa1291": "withdrawFundsAdvanced(address,uint256,uint256)", "0x8ed67a44": "setPrice(uint16)", "0x84ebde52": "Under_the_Hood()", "0x5a28340a": "accessOperatingBudget(uint256)", "0x9a89ad65": "within6Confirms(int256,int256)", "0xdfce8ac3": "fipsLegacyRegister(bytes20,address,bytes)", "0x73f310df": "multiAccessRemoveOwner(address)", "0x4cbee813": "logout(string)", "0xd992bd5b": "testResultNotZero()", "0x05b34410": "creationDate()", "0xfed4614b": "funeral(bytes,int256)", "0x58cb7323": "MainnetETCSurvey()", "0xbb504317": "divest(address,uint256)", "0x82381c96": "WatchCurrentMultiplier()", "0xcce81927": "EtherDice(address,address)", "0x70961774": "getBlockCreatedOn()", "0x84a7b223": "Canary(address)", "0x9378a9e2": "setUInt(uint256)", "0xe4360fc8": "getFileListElement(bytes)", "0xe597f402": "create(bytes1,bytes32,bytes)", "0x95d5a1be": "SignatureReg()", "0x33ce7787": "transferInvestorAccount(address,address)", "0x46c52b1a": "blockHexCoordsValid(int8,int8)", "0x3092afd5": "removeMinter(address)", "0x30945443": "update(address,string,string)", "0xc37ff3d9": "sha(uint256,uint256)", "0x29a6f31b": "oraclize_query(uint256,string,string[2],uint256)", "0x227f9633": "addOption(string,address,uint256)", "0x38eaf913": "setDirectorNode(string)", "0xab67aa58": "transferFrom(address,address,uint256,bytes)", "0x0ce3151c": "personUpdateRelation(uint256,string)", "0x216ef940": "proxyUpgrade(address,address,bytes)", "0x76bc21d9": "fireEventLog2Anonym()", "0xf004073a": "performAction(uint256)", "0xdba7ef7d": "Bookie(address,address)", "0xa0469b02": "inputToDigit(uint256)", "0x1d007f5f": "changeDAO(address)", "0x9dcb5c65": "resultsWeightedByEther()", "0x14ab9038": "setTTL(bytes32,uint64)", "0xf4d94699": "EndowmentRetriever()", "0xe74b9d11": "safeToSubtract(uint256,uint256)", "0xd7504385": "validateToAddress(address)", "0x57e2880d": "scheduleTransaction(uint256,uint256)", "0xe73a914c": "setDAO(address)", "0xc47bc007": "add_funds()", "0x37881810": "setCallbackAddress(address)", "0x686f2c90": "collectAllFees()", "0x278b8c0e": "cancelOrder(address,uint256,address,uint256,uint256,uint256,uint8,bytes32,bytes32)", "0xfac34ff6": "throwFoo()", "0x6d98e9fc": "totalWei()", "0xb0bcc610": "scheduleTransaction(address)", "0x665bcc32": "ProcessGames(uint256[],bool)", "0x3fd1f232": "LookAtAllTheseTastyFees()", "0xdd727ea6": "runJackpot()", "0x0acc4382": "getMinDailyWithdrawLimit()", "0x46b207b8": "checkExpiry()", "0xde5d953a": "logSingleIndex(bytes,bytes,uint256)", "0xf504e0da": "load_level(uint16)", "0x4b63e601": "scheduleCall(address,uint256,bytes)", "0x4a71d469": "collectRev()", "0x80db79d9": "StructAndFor()", "0x090637a1": "GetPart(bytes,uint256)", "0xc003b082": "getMyPlayerID()", "0x00a7d6b3": "checkTransferFromToICAP(address,bytes32,uint256)", "0xdcf8113e": "campaignEndedSuccessfully()", "0xd1af8a5a": "LinkerExample()", "0x01fd89a4": "getFlags(bytes20)", "0xa39a45b7": "replaceOwner(address)", "0x0a3b1cd2": "setHotwallet(address)", "0x075fe877": "scheduleCall(address,bytes,uint256,uint256)", "0x3e5fd9b5": "dEthereumlotteryNet(address,address,bool,address)", "0xa6403636": "resolve(uint8,bytes32,bytes32,bytes32)", "0x0b2acb3f": "add(address,bytes)", "0x6d522b19": "multiAccessChangeRequirementD(uint256,address)", "0x4311de8f": "ownerWithdraw()", "0xa99e7e29": "register(bytes,address)", "0x1ed6f423": "changeDescription(address,string)", "0xcd50d44f": "CheckRepresentment()", "0x4c0e207a": "__outputCallback(uint256)", "0xea8a1af0": "cancel()", "0x67387d6b": "testThrowCreateWithNonceExistingNonce()", "0xdc583801": "doubleyour5()", "0xb8077e28": "getTxOrigin()", "0xbfbc793c": "computeNameFuzzyHash(string)", "0x79baa8a9": "BasicIncome_CoFund()", "0xf4dbeb9d": "getCredRanksByContents(address,uint256[])", "0x227185d6": "Send1Get2()", "0x75c4aaa6": "addUnderDog(uint256)", "0xa7abc124": "activate(bool,bool)", "0x8df554b3": "Dividend()", "0x092b25e9": "setOwner(string,address)", "0x67af26fb": "transferOtherFrom(address,address,address,uint256)", "0x4bb278f3": "finalize()", "0xd1e15045": "sendBack()", "0xa4699cad": "resetWithdrawls()", "0xb61d27f6": "execute(address,uint256,bytes)", "0x9772c982": "scheduleCall(address,bytes4,bytes,uint256,uint256)", "0x1b3a8e6f": "directionCount(int256,int256,int256,int256)", "0xd499555b": "getFirstActiveDuel()", "0xb738169c": "betOnOddEven(bool,bool)", "0x411c4e72": "ModifyFeeFraction(uint256)", "0x06f36cc9": "helpBlue()", "0x9e65c7e5": "updateLatestRevision(bytes20,bytes)", "0xb47fa7e0": "DepositLimit(uint256)", "0xf1736d86": "m_dailyLimit()", "0x62ea82db": "bids(address)", "0x4166c1fd": "getElevation(uint8,uint8)", "0x8702735c": "setCapitol(uint256,uint256)", "0x3cc7790a": "GSI()", "0x83f6d9a4": "validateNameInternal(string)", "0x8d99b2eb": "endPoll()", "0x8bbda7e3": "setContent(string,bytes)", "0x52efea6e": "clear()", "0x2581c674": "testBitsOrFailIndexOOB()", "0x05d87fe2": "issueLetterOfCredit(uint256,uint256,uint256)", "0xcbf0b0c0": "kill(address)", "0xf83d96c1": "InsuranceAgent()", "0x8dd5e298": "canEnterPool(address)", "0x2d580ef6": "add(address,bytes32)", "0xeeda149c": "Register(address)", "0xcc25decd": "SampleOffer(address,bytes,uint256,uint256,uint256,uint256,uint256)", "0x428d64bd": "getShares(address,bytes32[])", "0x3c9a4baa": "requestOutput(bytes)", "0x8cae1374": "editBlock(uint8,uint8,uint256,int8[5])", "0x419db07b": "generousFee()", "0x202e3924": "getOperation(uint256)", "0x5ee345e4": "computeEndowment(uint256,uint256,uint256,uint256,uint256,uint256)", "0x7df23b6a": "ReleaseOracle(address[])", "0x9b2ea4bd": "setAddress(string,address)", "0x65093661": "newCommunity(address)", "0x33637d5a": "getPendingBlock(uint256)", "0x7910085d": "fipsIsRegistered(bytes20)", "0x730720b8": "testControllerValidTransfers()", "0xb0c80972": "setBalance(uint256,bool)", "0xdcf537b1": "multiply7(int256)", "0xdf5cc291": "get4(bytes,uint256)", "0x9ae4e388": "ChangeClientTokenAccount(address,bool)", "0x3121369d": "validateRequiredStackDepth(uint256)", "0x1747dfd4": "ContractPlay()", "0x598647f8": "bid(uint256,uint256)", "0xc368109c": "monster_hp(uint256)", "0x7fa22001": "assertEq0(bytes,bytes,bytes)", "0x8e280dce": "findNextYear(uint256,bytes)", "0x39d1f908": "actualBalance()", "0x8143f8a8": "totalGas(bytes)", "0xfe55932a": "setName(uint256,string)", "0x0fbf7151": "startsWith()", "0x4f20f35a": "payExpenses(address,uint256)", "0x705eeb90": "MultipleConstructorTest(bool)", "0x2df8e00d": "becomeMortal(uint256)", "0x645dce72": "updateRelease(uint32,uint32,uint32,bytes20,bool)", "0x1f6e5117": "getCallbackAddress()", "0xf51cbc72": "Level()", "0x64edfbf0": "purchase()", "0x35930e13": "setMinimalRewardedBalance(uint256)", "0x015e4f3a": "getConfigUint(int256,bytes)", "0x2c329e99": "Last_block_number_and_bloctime_used()", "0x6f3a7561": "SimpleAuction(address)", "0x6de00927": "GetUserRank(uint8,address)", "0xbe600276": "move(uint16)", "0x27d6c032": "unregister(bytes)", "0x4188d79c": "releaseExists(string,uint32,uint32,uint32,string,string)", "0x7ba38916": "changeAdminFromBoard(address)", "0x3369dace": "flipTheCoinAndWin()", "0xfa8dc33a": "checkRecordExists(bytes)", "0xebaf7f2f": "returnReward(uint256)", "0xc88961da": "createKingdom(string,address,address,address)", "0x21970c0c": "pay_royalty()", "0xb4a5ef58": "updateDefaultTimeoutPeriod(uint256)", "0x57bcccb6": "revokePermanentApproval(address)", "0xd1d1c8ae": "ConvertNumbers(bytes)", "0xc1c0e9c4": "exec()", "0xcc131be1": "CreateNewDraw(uint256)", "0x75f96ead": "Guess(uint256)", "0x8a5fb3ca": "currentFeePercentage()", "0x550bcd8d": "testThrowUpdateLatestRevisionEnforceRevisions()", "0xa6780857": "fireEventLog0Anonym()", "0x2d0104a5": "updateFirstDuel1(uint256)", "0xcbf1304d": "balances(address,uint256)", "0xdda9939c": "Store(address[])", "0xf41bfa9e": "mint(int256,uint256,string)", "0x044215c6": "token(uint256)", "0x1f903037": "getBytes32()", "0xa6f9dae1": "changeOwner(address)", "0xf9391d24": "AllPayAuction()", "0xabebb7f3": "MarketsContract()", "0x9e1e6528": "uncertify(address)", "0x81788e2b": "addAllowedAddress(address)", "0x4f44728d": "ownerChangeOwner(address)", "0x3da0ac79": "compare()", "0x96e438a1": "reclaimDeposit(uint256)", "0x5fe22c8b": "testFailTransferWithoutApproval()", "0x6835f32c": "build(bytes)", "0x5cac8b27": "amazing()", "0xad605729": "getParticipantCount()", "0xb6294bde": "AdminGetFee()", "0xec81e22e": "returnmoneycreator(uint8,uint256)", "0xc535165f": "revealAndPayout(bytes,bytes)", "0x6e0bd282": "destroy(bytes32)", "0xdda44b10": "buyRecipient(address,uint8,bytes32,bytes32)", "0xd4859dbc": "UniversalFunctionSecure(uint8,bytes32,bytes32,bytes32,bytes32,bytes32)", "0xd2602930": "RockPaperScissors()", "0xa08b3367": "EC()", "0x92d66313": "getYear(uint256)", "0xe49dcee9": "fixTokens()", "0x36555b85": "add(string,uint256)", "0x25010816": "get_length(uint256,uint256)", "0x610d5de8": "validateEndowment(uint256,uint256,uint256,uint256,uint256)", "0x79ce9fac": "transfer(bytes32,address)", "0x3ced516c": "descriptionHashes(bytes32)", "0xcf69df28": "getDataRequestLength()", "0x706dfe54": "getIssueState(uint256,bytes32)", "0x5af77fff": "Contract()", "0x66e5cb50": "stopTransfer(uint256)", "0x5f72f450": "check(uint256)", "0xf3b50c04": "rescind()", "0x57aee888": "_eraseNodeHierarchy(uint256,bytes32[],bytes32)", "0xaacc5a17": "getRandom()", "0x40275f85": "getPersonalDepositAddress(address)", "0x75700437": "query1_withGasLimit(uint256,string,string,uint256)", "0x6eb7b4c2": "underdogInfo(uint256)", "0x0f3eb785": "add(string,uint256,uint256,uint256)", "0xdc19266f": "Total_of_Players()", "0x9743dfc1": "jesterAutomaticCollectFee()", "0x6618b008": "cancelSellOrder(address)", "0x65538c73": "fireEventLog0()", "0xa4502cb8": "setExportFee(address,uint256)", "0x97bb2a63": "newvow(uint256,address)", "0xb400d149": "betOnNumber(uint8)", "0x030d406b": "entryPayout(uint256)", "0x1d71a1cd": "newIncome(string)", "0x85dd2148": "getSaleDate(bytes16)", "0x29917954": "exitPool()", "0xa25057de": "_transferToICAP(bytes32,uint256)", "0x24fc65ed": "getId(uint256,uint256)", "0x938199a5": "getDateOfLastPayment()", "0x04bb754c": "TradeFinancing()", "0xe37aa618": "distributeValue()", "0x547916ea": "finishRound()", "0xed01bf29": "budget()", "0x95ee1221": "isCancelled()", "0xfe777bcd": "etherForSale()", "0xffe302d1": "setPlz(string)", "0x891de9ed": "fromTLA(string)", "0x84734476": "copyBytes(bytes,uint256,uint256,bytes,uint256)", "0xfb114f57": "oraclize_query(uint256,string,string[3],uint256)", "0xceebe28d": "repoInterfaceVersion()", "0xb0ad38c4": "buildCity(string,uint256[2],uint256[2])", "0xefa7e56b": "GameEnds()", "0xcc3471af": "maxClaimBlock()", "0xa7c5052e": "buildDSTokenRegistry()", "0x6831c169": "totalPayedOut()", "0x98f3b81a": "getShares(address,bytes32[],int256[])", "0x2d077ad0": "Latch()", "0x0ac28725": "requestTradeDeal(uint256,uint256,string)", "0xb311ee0c": "refundClaimDeposit()", "0xadd82871": "strEqual(string,string)", "0x7879e19e": "CollectAllFees()", "0x5bd74490": "regProxy(address,address)", "0xd2b0d554": "getDisclaimer()", "0x0b74edc6": "testFinalHash()", "0x6cf761d4": "getMinConfirmationsByAddr(address)", "0x4cedf74e": "get_party1()", "0x4adcbd19": "isThisHardforkedVersion()", "0xefdecd9b": "check_withdrawdao()", "0x996a8046": "__callback(bytes32,string,bool)", "0x7c9cd7df": "changeDeveloper_only_Dev(address)", "0x3f77b560": "newDocument(bytes)", "0x06b5f02d": "calcWinnings(uint256,uint256)", "0x0a2282ae": "JackPot()", "0x378a2178": "tallyVotes()", "0xd8915fc5": "DCAssetBackend(bytes32,bytes32)", "0x0f590c36": "emergencyFixGameResult(uint64,uint256)", "0xea4af029": "ConferenceCertification()", "0x769dc523": "GetCategoryNumber(bytes4)", "0xd5df7559": "removeDocument(uint256)", "0x749aa2d9": "endRound()", "0xd8e5ae6a": "Etheramid()", "0xc0576b73": "monsters(uint256)", "0x32fefb4c": "add_account(address,address)", "0x7d619d9b": "holdCoin(address,address)", "0x5b067cce": "testCreateCostMain()", "0x384b1393": "follow(uint256)", "0x4162169f": "dao()", "0x5d8227e6": "FactoryBase(string,string,string)", "0x6bf52ffa": "Vote()", "0xeb5904c0": "setProfitDistributionContract(address)", "0x366a68dc": "setBlockLock(uint256)", "0x80d9eaa6": "refCount()", "0x89b8b492": "read(uint64)", "0x46b5e202": "set_num_levels(uint256,uint256)", "0xd96de4ce": "AdminDrawError()", "0x47b47102": "bakeCookie(string)", "0x1d7b5baf": "setUint(int256,bytes32,string,uint256)", "0x0699d07d": "updateMaxVal()", "0xfa544161": "getOwner(address)", "0x638560cf": "registerBool(address,bool)", "0x7c25f260": "Government()", "0x24a852c6": "unset(bytes)", "0xa32f0f41": "testFailControllerUnapprovedTransferFrom()", "0x0968f264": "withdraw(bytes)", "0x5f52e9fd": "WithdrawCashForHardwareReturn(uint256)", "0xc0f68859": "getMinimumGracePeriod()", "0x1bf6c21b": "USD()", "0x0fe234ed": "testSetController()", "0x05a17fc6": "getAccountFeed(address,uint256,uint256,uint256)", "0x673448dd": "isApproved(address)", "0x59dac714": "hashTo256(bytes)", "0x5a09f2f4": "setHouseFee(uint256)", "0x013cf08b": "proposals(uint256)", "0xeebf9808": "PiggyBank()", "0xadd43c59": "EtherTopDog()", "0xf909d60d": "getMinimumGasLimit()", "0xeb045789": "ChannelSeries(address)", "0x66d38203": "setup(address)", "0xe8641652": "strCompare(string,string)", "0x1959a002": "userInfo(address)", "0x737c8ea1": "_getRevisionBlockNumber(bytes32,uint256)", "0x127714c7": "getBudget()", "0x97daa043": "register(bytes,address,address,uint256,bytes)", "0xb5784f6f": "testMultiplePackages()", "0x0ce46c43": "scheduleCall(address,bytes4,bytes,uint16,uint8,uint256[5])", "0xe5782fd5": "setFeeStructure(uint256,uint256,uint256)", "0xa9f6def0": "HonestDice()", "0xeb7cdb56": "rankDown(uint256,uint256)", "0xb17b94c1": "testSystem()", "0xdd36e18b": "ContractStatus()", "0xee0dc478": "testSetEnforceRevisions()", "0x918359c6": "needsBirth()", "0xa5b1e13d": "settle(address,address,uint256,uint256)", "0x6b76484e": "swap(address,address)", "0x68402460": "scheduleCall(address,bytes4,uint256,uint256,uint8,uint256)", "0x733480b7": "transferToICAP(bytes32,uint256)", "0x567dbf18": "__forward(address,uint256,uint256,bytes)", "0x73e1743a": "buildDSBasicAuthority()", "0x482961e1": "updateReading(uint256,uint256)", "0x4e6ba0a9": "testCreateCostMultisig()", "0x8d7108e5": "isValidLocation(uint8,uint8,int8[5],int8[24])", "0x10142785": "assign(bytes,uint256,bytes1)", "0xfe97ee88": "hasPhone(address)", "0xe2861c8d": "cashOutProfit()", "0x0fa9ced4": "emergencyFuneral()", "0x8389f353": "setNumCities(uint256)", "0xdba1ac3d": "getEnforceRevisions(bytes20)", "0x1b4fa6ab": "getDefaultStackCheck()", "0x79be02af": "Read(address)", "0x70844f7a": "sendBadge(address,uint256)", "0x7bfaad96": "addNode(bytes,address)", "0x4d782cbc": "executeSellOrder()", "0xbe71248a": "payWinner()", "0x41304fac": "log(string)", "0x4f059a43": "getClaimAmountForBlock()", "0x6d2cb794": "airaTransfer(address,address,uint256)", "0x5a5383ac": "canExitPool()", "0xcabd27de": "Motion(address)", "0x433d4aab": "resolve(uint8,uint8)", "0x7d89ae63": "__findRef(string)", "0x4e1053cc": "RobinHoodPonzi()", "0x0220a5b4": "terminate(string)", "0x419ffa03": "fipsRegister(address)", "0x77a7e6be": "getRefTotal(uint256)", "0xed64bea4": "JamCoin()", "0x3cf885c4": "isBitSet(uint256,uint8)", "0xf2da67db": "setMany(uint256,int256,uint256,bytes20,address,bytes)", "0xe26c8434": "AdminStartDraw(string,bytes)", "0x13df7091": "mintAll(int256)", "0x8a46bf6d": "testFallback()", "0x29bed3bf": "EthereumRoulette()", "0xf869b11a": "declareVictor(uint256,uint256)", "0x45c41478": "getMarkets(bytes,address)", "0x90cf581c": "voteYes()", "0x9ec35352": "returnRandom()", "0x5025b9ae": "expire(uint256,uint256,uint8,bytes,bytes,bytes)", "0x338a1379": "_setPackedBlockNumber(bytes20,uint256)", "0xdb641ab4": "Game_balance_in_Ethers()", "0xbc0e7adb": "testThrowsDisownNotOwner()", "0x302d350e": "firstChainedCallback(uint256)", "0x5af73f3f": "getMinimalBalance(uint256,address)", "0x4c488dac": "getChannelValidUntil(bytes)", "0xa1616429": "testBitOrSuccess()", "0xaa64c43b": "transferPool(address,address,uint256)", "0x78e97925": "startTime()", "0xa0355f4e": "decline(uint256)", "0x02556de3": "updateMajorTree(bytes32)", "0x01984892": "name(address)", "0xfad4b99a": "updateChannelMinimum(address,uint256)", "0x4f39ca59": "drop(bytes32)", "0x61591a7c": "personUpdateDOB(uint256,int256)", "0x17b3a34b": "_addIdentities(uint256,bytes32[])", "0x91d8b14e": "BuyTickets()", "0x1aadcc34": "convertGreyGreen(uint8,uint8)", "0x9a863892": "NewProposal(uint256)", "0xc5487661": "proxyTransferToICAPWithReference(bytes32,uint256,string)", "0x85f8c16d": "claimHours(int256)", "0xa71f94c8": "scheduleSetUInt(address,uint256,uint256)", "0x7ef09476": "transfer(uint64,address)", "0x94bcdb4c": "Example2()", "0x37930615": "extend(bytes16[],uint64)", "0xfb09b1ac": "testBalanceOfReflectsTransfer()", "0x19a278b9": "getBAddress()", "0xc0a239e3": "valuePerShare()", "0xa039e3c7": "testSetNotTransferable()", "0x22593300": "Small(address)", "0xe21608be": "ReserveToken()", "0xc2985578": "foo()", "0xb463bcde": "testThrowsSetNotTransferableNotOwner()", "0x88d695b2": "batchTransfer(address[],uint256[])", "0x37b0574a": "isClassic()", "0x1da6822c": "testThrowsTransferEnableNotTransferable()", "0xdcfa9cc0": "testProxyCall()", "0x478aa69e": "unauthorizeUser(address)", "0x102accc1": "fireEventLog2()", "0xed62cf1f": "setCanCall(address,address,bytes,bool)", "0x15f73331": "invalidateName(string)", "0x73e30e49": "majorEventFunc(uint256,bytes,bytes)", "0x00c721ab": "setHand(uint256)", "0xf9e27106": "investmentEntryCost()", "0x4c7f74df": "EtherDelta(address,address,address,uint256,uint256,uint256)", "0xb74e452b": "today()", "0xd3118a5a": "addDoc(string,string)", "0xc204f9f1": "_transferFromToICAP(address,bytes32,uint256)", "0xf50d3914": "resetFoundationtList()", "0xe67cdfb7": "moveOldUser(uint256)", "0x98eaca94": "inKissBTC(uint256)", "0xc633084f": "sendGreeting(address,string)", "0xde10f04b": "eraseNode(bytes32[])", "0xd50495f4": "addTransaction(bytes)", "0x96cff3df": "getMinimumCallCost(uint256,uint256)", "0xce373b95": "heroOfThePit()", "0x39e525f9": "resolveCallback(uint256)", "0x942b90d3": "getRewardTable()", "0xedca914c": "buyTicket()", "0x5fcb568c": "release(string,uint32,uint32,uint32,string,string,string)", "0x6c9c2faf": "getSupply()", "0xf1448e10": "requestExecution(bytes)", "0x0c08bf88": "terminate()", "0x08aba5aa": "setAccountBalance(uint256)", "0x2c46d8d5": "EndRound(uint256)", "0x3d5db1c2": "incrUserOnholdBal(address,uint256,bool)", "0xf2f254c7": "getLatestMinorTree(bytes32,uint32)", "0x373a1bc3": "scheduleCall(address,bytes4)", "0x3a96fdd7": "compare(string,string)", "0x738ddabe": "getContentIndexedAccountCred(uint256,address,address)", "0x5acce36b": "getEndowmentBalance()", "0x1ca60aeb": "setMeltingContract(address)", "0x52375093": "m_lastDay()", "0x565a2e2c": "getBeneficiary()", "0x9d5c6061": "getMsgGas()", "0x41d31feb": "get_read_only_keys()", "0x796b89b9": "getBlockTimestamp()", "0x4a41e045": "getUint8(int8)", "0x38e48f06": "save(string)", "0x1cda37f2": "eraseRecords(bytes32)", "0xae978f08": "getLatestTweet()", "0x20909fa0": "communityCurrency()", "0xafbec8df": "TheGrid()", "0x1c14179a": "GavCoin()", "0x0b6142fc": "breach()", "0x3ab1e703": "roundMoneyDown3SF(uint256)", "0x414ceac0": "investorAddFee(uint256)", "0x82a62137": "activateAccount(address)", "0x4ca7fbd0": "updateTokenPriceWeekTwo()", "0x2551858e": "getFlags(bytes32)", "0x4ad07b0e": "oracleOutcomes(bytes32,address)", "0x60b431a4": "testGetSig()", "0xa5f8cdbb": "buyTicket(address)", "0x64aabe92": "tryExec(address,bytes,uint256)", "0xa6c01cfd": "isInGeneration(uint256)", "0x149c5066": "ChanceOfWinning(uint256)", "0xc068eae0": "player_collect_winnings(uint256)", "0x8129fc1c": "initialize()", "0xcf832ce2": "ownerRefundPlayer(bytes32,address,uint256,uint256)", "0x3517a740": "getNodeParent(bytes)", "0xec6afc22": "oraclize_query(uint256,string,string[3])", "0x50944a8f": "setMembership(address)", "0x85b1423e": "returnAll()", "0xd95a2d42": "lendGovernmentMoney(address)", "0x347632e8": "getShareholderAdressByID(uint256)", "0xbb39a960": "trade(address,uint256,address,uint256)", "0x8abadb6b": "setAccountLevel(address,uint256)", "0xa502aae8": "getNextGenerationId()", "0xb5bfdd73": "addDSource(string,bytes1,uint256)", "0x28d3ad3f": "getPot(uint256)", "0x08933d11": "getJoinBlock(address)", "0x8383bfc8": "EscrowFoundry()", "0x2ca15122": "sign()", "0xf340fa01": "deposit(address)", "0x9ed93318": "create(address)", "0xa1c0539d": "scheduleCall(address,bytes4,bytes)", "0xced92670": "changeMultiplier(uint256)", "0xb2c652f3": "getMarkets(uint256[128])", "0x69b144eb": "testThrowsCreateNewRevisionNotOwner()", "0x16c72721": "forked()", "0x712ca0f8": "getOrder(string)", "0x0cf45ba5": "updateFirstDuel2(uint256)", "0x4173b181": "setWeiPrice(uint256)", "0x689b3e2d": "Moonraker(address,address)", "0x8691162a": "TlcCoin()", "0x432ced04": "reserve(bytes32)", "0x38178fbe": "addString(string,string)", "0x8f1327c0": "getRound(uint256)", "0xa9eed530": "reduceOrderQty(uint256,uint256)", "0x408938d0": "testUpdatePackageDb()", "0x56105a08": "DgxSwap()", "0xc43d0575": "scheduleCall(bytes4,uint256)", "0xdba21657": "askForEther(uint256)", "0xca3b5c91": "hasRelation(bytes,bytes,address)", "0xc71cbcf3": "recoverAccount(address,address)", "0xb010d94a": "canExitPool(address)", "0x0a16697a": "targetBlock()", "0xff1f7046": "requiresAuction(string)", "0x0b811cb6": "executeProposal(uint256,bytes32)", "0xbb8be064": "HardwareToken()", "0xe2b05077": "getSaleDate(bytes,uint256)", "0x1e9a6950": "redeem(address,uint256)", "0xd21b84ac": "createNewDAO(address)", "0xd644e356": "index(uint256,address,uint256,uint256)", "0xea27a881": "getMinimumEndowment(uint256,uint256,uint256,uint256)", "0x99a88ec4": "upgrade(address,address)", "0xc8e4acef": "playerByAddress(address)", "0x0b7abf77": "TOTAL_TOKENS()", "0xfb5d7376": "step4()", "0xc0aa18e7": "History()", "0xe2233ada": "smartDoor(address[])", "0xd6006e88": "send(address[],uint256[],uint256)", "0x95671958": "getFileListTail()", "0x16bac350": "overthrow(string)", "0x5cb18a6d": "fipsLegacyRegisterMulti(bytes20[],address,bytes)", "0x60116397": "Registrar(address,bytes32,uint256)", "0x60fe47b1": "set(uint256)", "0x5f8f0483": "buyBankerAgreementFromImporterBank()", "0x4c8cc20b": "toContentID(address,string,string,address,uint256)", "0x45ca25ed": "changeName(address,string)", "0xb21bce4c": "vote(bytes,bool)", "0x334dc700": "CanaryV7Testnet()", "0xc31d0031": "CrowdFundDAO(string,uint8,string)", "0xf3d91708": "isEligibleForUpgrade(address)", "0x0ee07836": "adjustDifficulty(uint256)", "0xf6232556": "Security_GetNumberOfAttemptsToConnectBankAccountToANewOwnerAddress()", "0xb2d37e95": "remove_order(uint32)", "0x691d58e7": "_applyRefund(uint256)", "0x1c2353e1": "isCertifier(address)", "0xcf158fe9": "scheduleTransaction(uint256,uint256,uint256)", "0x5d96ec65": "setAdministrator(address,string,bool)", "0x0651844e": "activateBalance(address)", "0x217311ac": "getWords(uint64)", "0xc127c247": "addMember(address,string)", "0x40c0bcb9": "checkBetNumber(uint8,address,bytes32,bytes32)", "0xb633620c": "getTimestamp(uint256)", "0x5b764811": "_jMul(uint256,uint256,uint256,uint256)", "0xfe029156": "swap(address,address,uint256,uint256)", "0x31db4b95": "doTriggerAuth()", "0x203c03fa": "Coinflip()", "0x209a5b8a": "moneySumAtSettlement(address,uint256,int256,uint256)", "0xf10ae2ab": "__dig_then_proxy(uint256,address,bytes)", "0xd532e481": "activateFrozenAccount(address)", "0xe9a9c1b4": "get_party1_balance()", "0x8fcc9cfb": "setMinDeposit(uint256)", "0xe5c7e509": "testThrowTransferDisableNotEnabled()", "0x4e077f2a": "addGasEther()", "0xb7c93330": "ResourcePoolTester()", "0x82661dc4": "splitDAO(uint256,address)", "0x0e554bd8": "scheduleCall(bytes,uint256,uint256,uint8)", "0x49041903": "getGame(uint64)", "0x0e1da6c3": "claimTimeout()", "0xc53ad76f": "Kardashian()", "0x8b7bcc86": "numWinners()", "0x1043dcdf": "LastIsMe(uint256,uint256)", "0x6cd22eaf": "updateAuthority(address,bool)", "0xb796a339": "addRegistryIntoOwnerIndex(address,address)", "0x308d6613": "getSignData(uint256,uint8)", "0xed88c68e": "donate()", "0xb719d1d0": "getRegInfo(address)", "0xac8d6030": "removeRequest(address)", "0x46f0975a": "signers()", "0x434cb64c": "startNextGeneration()", "0x6cb3d30a": "triggerTryAuth()", "0x3c067945": "fundBalance()", "0x26c7edaa": "flip4(bytes)", "0xf76f950e": "uint2str(uint256)", "0x860e9960": "BetPriceLimit()", "0xb0ecca8f": "LookAtLastTimePerZone(uint256)", "0xa35cfa22": "make_move(uint256,uint8,uint8,uint8,uint8)", "0x3f74fecb": "DSTrueFallbackTest()", "0xdd2ad311": "scheduleCall(bytes,uint256)", "0x0ae5e739": "grantAccess(address)", "0x7d5fec5a": "setOwner(uint8,uint8,address)", "0x6a4b6aa5": "untrustedChildWithdraw()", "0x332f93a9": "nextPayoutGoal()", "0xc5ae6e0e": "Kernal()", "0x75438e49": "fillGas()", "0x51404cbe": "forceDivestOfOneInvestor(address)", "0xeacfc0ae": "Authorized()", "0xe59d843a": "Replicator(bytes,uint256,uint256,address)", "0xf00e8651": "createRequest(address[2],address,uint256[11],uint256,bytes)", "0x02acdb44": "setAnyoneCanCall(address,bytes4,bool)", "0x2a24f46c": "auctionEnd()", "0x7ef1925b": "getShareRange(uint256,uint8)", "0x2fac1a54": "newOrder(bool,uint256,uint256,uint256,uint256)", "0x56b8c724": "transfer(address,uint256,string)", "0x33fd066d": "doBalanceFor(address)", "0xf29617da": "registrationDeposit(address)", "0x2b297f9e": "registerDao(address)", "0x79cce1c5": "getReleaseHashes(uint256,uint256)", "0xbed1b8b9": "convertToInt(string)", "0xef5daf01": "_dumpToCompany()", "0x23dc42e7": "query1(uint256,string,string)", "0xa53b1c1e": "setInt256(int256)", "0xb8cf14e7": "updateStatusPlayer()", "0x61aa8d93": "processFee()", "0x10f41715": "updateMintingData(uint256,uint256)", "0x048e2e94": "getAccountSize(address,uint256)", "0x7c47965e": "isInCurrentGeneration()", "0x420a8ac8": "NanoPyramid()", "0xe56556a9": "getPlayerID(address)", "0x5cd2f4d3": "approve(address,bytes32)", "0x8da4d776": "newCommune(address)", "0x4d30b6be": "balanceOf(address,bytes32)", "0x4a606c53": "_db()", "0x4956eaf0": "deploy(address,uint256)", "0xf1fe42b8": "TransactionRequest(address[3],address,uint256[11],uint256,bytes)", "0x63e38ff3": "id_for_nym(uint256)", "0x0e757a2e": "testSetAndGet()", "0x3facd57c": "registerBill(uint256,address,address,uint256,uint256,uint256)", "0xe548cf13": "betOnColumn(bool,bool,bool)", "0x2f1e4968": "makeNewProposal(string,uint256)", "0x0b467b9b": "revoke(bytes)", "0x74bfb965": "addNewProxy(address)", "0x02de2cf3": "isLatestPreReleaseTree(bytes32,bytes32)", "0xfc1f7652": "_isBoardMember(address)", "0xefef39a1": "purchase(uint256)", "0x3ae9b510": "getLatestMajorTree(bytes32)", "0xc24924d6": "setQueryFee(uint256)", "0x839930ba": "getMinimumBet()", "0x8f5e9ca7": "acceptTOS(address,bool)", "0xd1100691": "BookCafe()", "0x839849c0": "changeBaseMultiplier(uint256)", "0x758971e8": "ownerTakeProfit(bool)", "0x2b785960": "testBitAndSuccess()", "0xd96a094a": "buy(uint256)", "0x379607f5": "claim(uint256)", "0x88e072b2": "checkTransfer(address,uint256)", "0x05fefda7": "setPrices(uint256,uint256)", "0xfc63d4fb": "order(bool,uint32,uint128)", "0x5718b994": "checkEvent(address,bytes,bytes,uint256)", "0x0c0662a8": "getLastWithdrawal()", "0xeb947f19": "ExampleResourcePool()", "0xb51c4f96": "getCodeSize(address)", "0x702fc7da": "ReviewModel()", "0xc6cb7a96": "orderMatchTest(uint256,uint256,int256,uint256,uint256,address,address,uint256,int256)", "0xb7760c8f": "transfer(uint256,address)", "0x32b12eac": "setFallback(address)", "0x0a4d564c": "TieUpLooseEnds()", "0xc3ad5ecb": "getTweet(uint256)", "0xe86afde0": "description(uint64)", "0xd0549602": "scheduleTransaction(address,uint256,uint256,uint256)", "0xbf2e694f": "getPreviousRequest(address,address)", "0x2525f5c1": "cancelBid(address,bytes32)", "0x19f02ceb": "set(address,address,uint256)", "0xf00acc47": "prepareRoll(uint256,uint256)", "0x29d28aad": "Broker(address)", "0x041d0c0b": "MyTokenLoad(uint256,string,uint8,string,address)", "0xd81ab0c1": "invoke(uint256,address,address,bytes)", "0xab09ee80": "respond(uint256,uint256,uint256,uint256)", "0xd985f122": "RelayToolsTest()", "0xbe0638e4": "WealthShare()", "0x5263ba87": "getLatestPatchTree(bytes32,uint32,uint32)", "0xb7bae9b7": "exists(bytes,bytes)", "0x0b80f8d3": "invmod(uint256,uint256)", "0xbb4d7cd1": "tag(uint256,string)", "0xadf54e0c": "betOnLowHigh(bool,bool)", "0xed54746e": "lastAuction()", "0xf158458c": "getMinimumEndowment(uint256,uint256)", "0x5fcc2edb": "IndividualityTokenRoot(address)", "0x7cc48875": "Slots()", "0x2885b593": "extractMasterKeyIndexLength()", "0x8940aebe": "publicKey(uint256)", "0x0aece23c": "getFeeAmount(int256)", "0x72c3015c": "mint(int256,address,string)", "0xd6a619e3": "transferIfPuritanical(address)", "0xe30443bc": "setBalance(address,uint256)", "0x1277e24f": "payOneTimeFee()", "0xb958a5e1": "getPhoneByAddress(address)", "0x4e71d92d": "claim()", "0x3e0d4f4a": "ApproveContractorProposal()", "0x18160ddd": "totalSupply()", "0x150ad2a8": "owner_transfer_ownership(address)", "0xa2b5591c": "oraclize_query(uint256,string,string[],uint256)", "0x8d227fc0": "getPeriodInfo()", "0x1c0b6367": "processTransaction(bytes,uint256)", "0xf245b9e4": "DVIP(address)", "0x392327b5": "owner_set_fraction(uint256)", "0xadaccd74": "getNickname(address)", "0x2e0ef395": "voteOnNewEntryFees_only_VIP(uint8)", "0x89c19ddb": "concat(string,string)", "0xcef8d343": "buyShare(uint256,bool)", "0xd224118f": "PrepareDraw()", "0x4269d8ef": "_safeSend(address,uint256)", "0xda1441cd": "KudosBank(uint256)", "0x7ccfd45a": "removeSubUser(address)", "0xcc70bb1a": "publish(string,string,string,address)", "0x708f29a6": "getTotalPayments()", "0x05459f42": "WeeklyLotteryB(address)", "0x452d44dc": "checkBothNotNull()", "0x659fb968": "getOracleOutcomes(bytes32[],address[])", "0x3570c2ee": "PosRewards()", "0xbca86986": "testSetup()", "0xff49b26e": "createEvent(uint256,uint256,uint8,uint32,address,uint256,uint8)", "0x541d920c": "commit(bytes,string)", "0xa6a20ff6": "DSEasyMultisig(uint256,uint256,uint256,uint256)", "0x0f5381f1": "testUserCanIncreaseVersionNumber()", "0xf8f46b5f": "getCurrentMinerAddress()", "0xfcfff16f": "open()", "0x5a9b0b89": "getInfo()", "0xb8017221": "get_party2_balance()", "0x514dcfe3": "seller_accept()", "0x2004dff6": "Basics()", "0x0b6d8d52": "createDAO(address,uint256,uint256)", "0xf18d20be": "adminWithdraw()", "0x8f9df278": "newEntry(int256,bool,uint256,int256,string,bytes32,address,uint256[])", "0x75949c13": "sendHalf(address)", "0x64ac2c4a": "WavesPresale()", "0x8946d33f": "SplitterEthToEtc()", "0x11400d8e": "priv_fastGetBlockHash__(int256,int256)", "0x7266f4a4": "X3()", "0xb189ad2a": "testErrorUnauthorizedAfterTransfer()", "0x31c2bd0b": "propose(address,bytes,uint256)", "0x100c8ada": "setCAmodulus(bytes)", "0x296ed88f": "testFailControllerInsufficientFundsTransferFrom()", "0xd5dbb1ad": "solveBet(address,uint8,bool,uint8,bytes32,bytes32)", "0x8a9ffb90": "transfer(string,string,bool)", "0x968908a3": "createMarketMaker(uint256,uint16,uint256)", "0x7b02b2c9": "sendMsg(address,string)", "0xa33dd801": "setTreasuryBalance(uint256)", "0x2f553d31": "isCreated(bytes32)", "0xf712d7ff": "testFailControllerTransferFromWithoutApproval()", "0xe51ff1fc": "iterateOverThings()", "0x60fd902c": "gnosisToken()", "0x2ef875fb": "div10(uint256,uint8)", "0x640f244b": "findSuitableGen()", "0x16cb9a01": "assertFalse(bool,bytes)", "0xe671f510": "onEtherandomExec(bytes32,bytes32,uint256)", "0x758b5172": "setPlayersPerRound(uint256)", "0x6423db34": "Reset()", "0x21958a50": "AddressSeries(address)", "0xfb87d5ea": "TransactionRequest(address[4],address,uint256[11],uint256,bytes)", "0xfb279ef3": "tip(uint256,address,uint256)", "0x338cdca1": "request()", "0x4e7ad367": "fireEventLog1Anonym()", "0xbd9335c0": "scheduleHangouts()", "0x4cb85356": "BranchSender(uint256,bytes32)", "0x1d7e1f68": "getContentRank(address,uint256)", "0x1a1df394": "Play(bool)", "0x468129a5": "setUnit(uint256,uint256,uint256)", "0xecb70fb7": "hasEnded()", "0x2d49ffcd": "getLocked()", "0x2e06c756": "post(string,string,string,uint256,uint256,address)", "0x73f93a48": "getAccountContentTip(address,uint256)", "0xf6a3d24e": "exists(address)", "0x5fbddcf3": "isLivingMonarch()", "0x6d568c43": "weiToCents(uint256)", "0xacf4280c": "buildDSApprovalDB()", "0xf3541901": "execute(address,bytes,uint256,uint256)", "0x88eb7af7": "_isHuman()", "0x48a490fb": "transferFromTreasury(address,uint256)", "0x5e03d393": "setAccountFrozenStatus(address,bool)", "0xfc687311": "betOn(int8)", "0x5bbfe9b6": "_myGroupHelper()", "0x5629c6d9": "doExecution(address)", "0xe3a9b508": "EnableDisableTokenProxy()", "0x9229c504": "new_mainPlayer(address)", "0x6f6c0244": "generateShortLink()", "0x33613cbe": "getBondBalance(address)", "0x4229616d": "collectPercentOfFees(uint256)", "0x4ed3885e": "set(string)", "0x043bb5e7": "getIdentities(address[])", "0xad2fea7c": "removeMinter(int256,address)", "0x0b7e9c44": "payout(address)", "0x17f5de95": "MAX_TOKENS_SOLD()", "0x50ea1932": "lookupISO3116_1_alpha_2(bytes)", "0x96f7807a": "getDuel2(uint256)", "0xa97ffd5e": "safeToSell(uint256)", "0x2f4ee5d4": "registerThrone(bytes,uint256,address,uint256,uint256)", "0x4c0bcfe5": "getTransferableBalance(address)", "0x0d17bc2e": "_disallow()", "0x0ca7395f": "returnFund(address,uint256)", "0x69fe0e2d": "setFee(uint256)", "0xfaf27bca": "greeter(string)", "0x0c7de59d": "edit(address,bytes,bool)", "0x16e27349": "getFeeRecipient(int256,int256)", "0x37751b35": "doTransfer(address,address,uint256)", "0x67fc1c6a": "validateProposedMonarchName(string)", "0xf59f99ee": "createNextGeneration()", "0x6be505f5": "selectWinner(bytes32)", "0xf6bd5893": "getGas(uint256)", "0x35b09a6e": "someFunction()", "0xb3aaa277": "validate(address[4],address,uint256[11],uint256,bytes,uint256)", "0x4f052648": "XaurumDataContract()", "0x117b4705": "retract(bytes32)", "0x2145e36c": "testBitSetFailIndexOOB()", "0x3d750b28": "found()", "0x1334a5e2": "eventCallback(uint8,address,address,uint256)", "0x3c2c21a0": "scheduleCall(address,uint256,bytes4)", "0x82996d9f": "rent()", "0xaf640d0f": "id()", "0xdaf22f4d": "identify(bytes32)", "0xfe4667e9": "getMaxLossAfterTrade(address,uint256,uint256,int256,int256)", "0xfc108f70": "GamblerPerAddress(address)", "0x89f4ed7a": "getLastTag(uint256)", "0xfcc11241": "addOrder(uint256,uint256,uint256,uint256,uint256,uint8)", "0x43243797": "fundsOf(address)", "0x892c0214": "NumberOfCurrentBlockMiners()", "0xb5a6c525": "extractFrozenAccountLength()", "0x1acb2719": "getNextRequest(address,address)", "0xa89a4f09": "creatorBalanceChecker()", "0x1e83409a": "claim(address)", "0x5e1d7ae4": "changeFeeRebate(uint256)", "0xb7482509": "deposit(address,string)", "0xfb47a067": "_getRevisionBlockNumber(bytes20,uint256)", "0x5dcdddd1": "testSafeToAddFix()", "0x9aa26f06": "registerBytes32(address,bytes)", "0xd085e66e": "GetPart(bytes32,uint256)", "0x2cd78450": "activateExportFeeChargeRecord(address)", "0x35d129f6": "untag(string)", "0x1a7a98e2": "getDomain(uint256)", "0x877653f0": "_storeBalanceRecord(address)", "0x446fbcd1": "CredSign()", "0xfae8f9a2": "setInitialParent(int256,int256,int256,int256,int256,int256)", "0xc1b056b0": "getNodeLeftChild(bytes)", "0x71f297cc": "XaurumToken(address)", "0xe3ffc9a3": "sendEtherToOwner()", "0xeccf1b29": "CrystalDoubler()", "0x57f4d5ec": "processDividends(address,uint256)", "0x75c589a0": "getMinimumCallCost()", "0x66772438": "computeResponse(uint16)", "0x7fefde53": "WillRegistry()", "0x8f4fb958": "calculateRandomNumberByBlockhash(uint256,address)", "0xed498fa8": "userTokens(address)", "0x5601eaea": "execute(uint256,uint256)", "0x8dd8596c": "sendDonation()", "0x15a0df43": "testThrowCreateNewRevisionNotOwner()", "0x0382c254": "CheckHash(uint8,uint8,uint8,uint8,bytes32)", "0x157f8f51": "feePaid(int256,int256,int256,int256)", "0xf00aac7f": "ArrayRR()", "0x7b7d7225": "_approve(address,uint256)", "0x54ed7b6e": "addHash(bytes)", "0x235c002d": "transferOther(address,address,uint256)", "0x7057c20d": "CFD(address)", "0xd5563f31": "createAuction(uint256)", "0x46c3166f": "testThrowRetractLatestRevisionNotOwner()", "0x4420e486": "register(address)", "0x9a969768": "distributeProfits(uint256)", "0x464f37c9": "trustedChildRefund()", "0x5d495aea": "pickWinner()", "0xdf55b41a": "owner(string)", "0x10e6e06c": "vote(bool,uint256)", "0xe7faecec": "testFailInsufficientFundsTransfers()", "0xea98e540": "proxyTransferFromToICAPWithReference(address,bytes32,uint256,string)", "0xfff78f9c": "doThrow()", "0x9bb01b5f": "ElcoinDb(address)", "0xdc6dd152": "playerRollDice(uint256)", "0x5d0be9de": "softWithdrawRevenueFor(address)", "0xcd591822": "CanaryV7Fast()", "0x36e6b92e": "taskProcessedWithCosting(uint256,uint256)", "0x7bb6a4c6": "uno(uint256)", "0x03427656": "getDefaultSoftResolutionBlocks()", "0xc1fd4339": "createMarket(bytes32,uint256,uint256,address)", "0xeb95b7d5": "Bounty(address,address)", "0x4dd49ab4": "get(bytes,uint256)", "0xfa6d373c": "LeaderHash()", "0x8c8d98a0": "toTimestamp(uint16,uint8,uint8)", "0x9a79f4a8": "testFailHeaderInsufficientFee()", "0xdd90c403": "getAccountFeed(address,uint256,uint256)", "0x58e59c32": "get_entry(uint256,uint256,uint256)", "0xfd747c0b": "rsaVerify(bytes,bytes,uint256,bytes)", "0x0f4cf692": "numMessages()", "0x18433bb7": "DrawPrepare()", "0x1dea0c57": "getRealBet(uint256)", "0x7d60e343": "getFileListSize()", "0xd24ddcfe": "buyKissBTC()", "0xa055fe64": "_projectCommitNew(address)", "0x5f17114e": "TimeDeposit()", "0x85fe0448": "testThrowRestartNotUpdatable()", "0x901717d1": "one()", "0x528fd7b0": "manualPayExpiredDuel()", "0x85952454": "newOwner(address)", "0xf34c7010": "commitSecurity(address,uint256,uint256)", "0x3bf2313d": "__transferToICAPWithReference(bytes32,uint256,string)", "0x67b830ad": "fillOrder(uint256)", "0x73fac6f0": "confirmReceived()", "0xf1b3f968": "getRaceEndBlock()", "0xf99fc046": "dEthereumlotteryNet()", "0xb409da05": "logDoubleIndex(bytes,bytes,bytes,uint256)", "0x9e920587": "testOwnedAuth()", "0x8e7cb6e1": "getIndex(uint256)", "0xe2f8a017": "payInstallment(uint256)", "0xac3e6b2f": "testSetNotRetractable()", "0x8fbc3ecd": "BUFFER()", "0x4b729aff": "buyNumber(uint256)", "0x166c4b85": "len(bytes32)", "0x6299f8cf": "stop(uint256)", "0xd767aee0": "bbb()", "0x29090202": "Resolver(address)", "0xcc2c2bcf": "MotionFactory(string,string,string)", "0xfd260dfc": "getCertificationDbStatus(address)", "0x30aceb89": "validateRequestParams(address[3],address,uint256[11],uint256,bytes,uint256)", "0xd11f13df": "numberOfParticipantsWaitingForPayout()", "0xd05c78da": "safeMul(uint256,uint256)", "0x69953501": "setUtils(address)", "0xff7f5f2a": "EtherizationUtils2()", "0xde4b3262": "setBasePrice(uint256)", "0x6cf9cc58": "registerResource(bytes,uint256,bytes,string)", "0x7ac37d58": "ownerTransferEther(address,uint256)", "0x0121b93f": "vote(uint256)", "0x07b6f631": "testTestHarnessAuth()", "0x869b3f6a": "testThrowsRetractNotOwner()", "0x18253234": "ticketsAvailable()", "0x5581004d": "createThrone(bytes,uint256,uint256,uint256,uint256)", "0x7102c138": "Standard_Token(uint256)", "0xce5566c5": "cash(uint256,uint256)", "0x16a25cbd": "ttl(bytes32)", "0x0c9fcec9": "setApproval(address,address,uint256)", "0xe6d95eb8": "DSAuthorized()", "0x34c0d654": "setPackageDb(address)", "0x0ee79fb3": "closeReferendums()", "0xe2cdd42a": "vote(uint256,address,bool)", "0xd3f297d6": "claimLiquidityReward()", "0xf37b437b": "scheduleCall(address,bytes,uint256,uint256,uint8,uint256,uint256)", "0x2090cf8b": "consultBalance(address)", "0xc9296d14": "scheduleTransaction(address,uint256,uint256,uint256,bytes)", "0x7993e5c2": "Devcon2TokenForTesting()", "0x268bb78e": "propose(address,bytes,uint256,uint256)", "0x2b16b56a": "setIndex(uint256,uint256)", "0x6d15f208": "reject(string,uint256,uint16,address,uint256)", "0xc4ff3614": "Wallet(address[],uint256,uint256)", "0xb47d89ad": "Details()", "0x0ae08793": "confirmAndCheck(bytes32)", "0x061e494f": "getBet(uint256)", "0x314e99a2": "abdicate()", "0xe487eb58": "getOwner(bytes20)", "0x7ee65635": "LookAtDepositsToPlay()", "0x9e9d3aa4": "FirstBloodToken(address,address,uint256,uint256)", "0x4dfd1b02": "setUint8(int8,uint8)", "0x82fbdc9c": "register(bytes)", "0xa8b60b93": "ackMsg(uint256,string)", "0x081e806d": "PayOut(uint256)", "0x8bab8791": "testPkgUpdate()", "0xc262df45": "isKnownRequest(address,address)", "0x4123cb6b": "m_numOwners()", "0x62be3172": "Message(address,address,address,string)", "0x0d368fee": "deverify(address)", "0x5f1231ea": "getMemberInfo(address)", "0xa07daa65": "newRequest(uint256)", "0xa4406bcd": "placeSellOrder(uint256,uint256)", "0x5b7d47a9": "betOnColor(bool,bool)", "0x9c6034a7": "sendIfNotForked()", "0x26a4861c": "CROWDFUNDING_PERIOD()", "0xbaac5300": "createTokenProxy(address)", "0xfc72c1ef": "ERC20Base(uint256)", "0x316b08a0": "scheduleTransaction(address,bytes,uint256[7],uint256)", "0x0b590c6b": "SingularDTVToken()", "0x750e443a": "voteAgainst(uint256)", "0xfc7b9c18": "totalDebt()", "0x7ff9b596": "tokenPrice()", "0xd67cbec9": "release(uint32,uint32,uint32,bytes20)", "0x553cc48d": "Player(string)", "0x579cdf17": "getAdminName(address)", "0x7e1c4205": "query2(uint256,string,string,string,uint256)", "0x54107401": "declareLove(string,string)", "0xea4ba8eb": "getOutcome(bytes)", "0x9b5adea2": "setMinter()", "0x185061da": "undoIt()", "0x90c3a370": "AuctionMaster()", "0xbd02e4f6": "calcRandomNumberAndGetPreliminaryGameResult(uint256,uint64)", "0xbc126ba1": "totalCents()", "0xa3747fef": "register(bytes,bytes)", "0x805210b7": "AmIPlayer2()", "0x4e05ded6": "ClassicCheck()", "0xec2ac54e": "deposit(address,uint256,bytes32,uint256)", "0x49c15bd9": "Purchase()", "0x87bb7ae0": "getTicketPrice()", "0xf2f03877": "commit(uint256,bytes32)", "0x167d3e9c": "SetOwner(address)", "0x5c634241": "CanaryV6()", "0xba15e52e": "getInfo(bytes20)", "0x06c1df7b": "checkBetColumn(uint8)", "0xf7bc39bf": "owns(address)", "0x27b752b8": "sha3HexAddress(address)", "0x5ac801fe": "setName(bytes32)", "0x1ae460e5": "isInPool()", "0x85c7a953": "WithdrawFullBalanceFromBankAccount()", "0x755b5b75": "setNumUnits(uint256,uint256)", "0xaefc8c72": "unsealBid(bytes32,address,uint256,bytes32)", "0xfab43cb1": "getPongAddress()", "0x9e997121": "getConfigAddress(bytes)", "0xda2b7416": "testBitsAndFailIndexOOB()", "0xd0e30db0": "deposit()", "0x4a0d89ba": "getSwap(uint256)", "0x63a9c3d7": "verify(address)", "0x337b1cf9": "setIpfsHash(bytes)", "0xbaf00f76": "removeAllSubUsers()", "0x1b370abb": "getPreviousNode(bytes)", "0x741e2345": "registerMany(address,uint256,int256,uint256,bytes20,address,bytes)", "0xb3760c80": "orderMatch(uint256,uint256,uint256,int256,uint256,uint256,address,uint8,bytes,bytes,int256)", "0x73ffd969": "setMap(uint256,uint256,uint256)", "0x50b44712": "tickets(uint256)", "0x6a9d2afd": "playToWinTest(uint256)", "0x644998ae": "maintain(int256,uint256,uint256)", "0x2203ab56": "ABI(bytes32,uint256)", "0x9c67f06f": "registryStarted()", "0x93423e9c": "getAccountBalance(address)", "0x524e4e61": "testDistribution()", "0xa17042cc": "getMsgValue()", "0x4f9d719e": "testEvent()", "0xcbcaacab": "checkTransferWithReference(address,uint256,string)", "0x1bcf5758": "getOccupies(uint8)", "0x1d4b0796": "updateTxStats()", "0xd173707d": "hasPhysicalAddress(address)", "0xa54a2b8b": "testBlockHashFetch()", "0xc51bf934": "CEILING()", "0x0e54b872": "registerUser(string,address)", "0xba51a6df": "changeRequirement(uint256)", "0x6bd92f7c": "activateAllowanceRecord(address,address)", "0x90daaf67": "getMinimalDeposit()", "0x85d5c971": "logTransfer(address,address,bytes32)", "0x8b9e5385": "MeterSlock(uint256,uint256,address)", "0x7bcd7fad": "getRecordAtIndex(uint256)", "0x8e035ac1": "BetOnHashV82()", "0x5dbe47e8": "contains(address)", "0x21bb79fe": "luckyDogInfo()", "0xdd467064": "lock(uint256)", "0xa4beffa7": "increaseInvestment()", "0x8400c307": "isRecipientAllowed(address)", "0x0965bf7d": "processProposals()", "0x4b5dc8cb": "roundMoneyDown3SFExt(uint256)", "0x777feff5": "getCertificationDbAtIndex(uint256)", "0x9462eae5": "ChangeContractor(address)", "0x4c6d1d9e": "checkOutTag(string)", "0x09a399a7": "personAdd(string,int256,int256,string)", "0x31380c89": "TokenSale()", "0xd0bff051": "testSetBalanceDb()", "0x4c738909": "getMyBalance()", "0xb2310cc5": "payRequstedSum(uint256,uint256)", "0xda3c300d": "currentFee()", "0x6ed7c013": "move_monsters()", "0x4f073130": "takeOrder(bool,uint256,uint256)", "0x6860fd58": "Fees(uint256)", "0x214c9d50": "WritedrawStatus()", "0xf314bf46": "setReleaseDb(address)", "0x561a4873": "buyAd(string,string,string,uint256,uint8,address)", "0xf249cf19": "get_all_challenges()", "0xbbed7177": "getContentTimestamp(uint256)", "0xc864e760": "recordCommissionEarned(uint256)", "0x1896f70a": "setResolver(bytes32,address)", "0xfd35e71b": "entryPayoutDue(uint256)", "0x5a58cd4c": "deleteContract()", "0xb29b5366": "setRentable(bool)", "0xad5c613d": "purchase(bytes)", "0x6949a058": "sendOwnerEther()", "0xc03e382f": "calculateShare()", "0xf5bade66": "setDeposit(uint256)", "0x384e5018": "etherandomCallbackAddress()", "0xf06186c7": "testReality()", "0x677342ce": "sqrt(uint256)", "0x10e89b22": "remove_deal(uint32)", "0xf2b445ad": "rowround(uint256,uint256)", "0xd7ed7453": "redeemWinnings(uint256)", "0x92b4bb50": "rps()", "0x089327de": "MyToken()", "0x87ebd76c": "initContract(string,string,uint256,uint256)", "0xdbc45228": "newProposal(address,uint256,bytes,bytes)", "0x6b1feeeb": "get_my_sig()", "0x6837ff1e": "newContract(address)", "0x9f181b5e": "tokenCount()", "0x92ba4ba6": "GridMember(string,uint256,bool,address,address)", "0x45755dd6": "returnFunds(uint256)", "0xb4b9d1f1": "lookup(uint256,uint256)", "0x98024f18": "testThrowsTransferDisableNotEnabled()", "0x9e7b8d61": "giveRightToVote(address)", "0x8112821f": "EthVentures()", "0xe65d6b49": "getCommission()", "0x068c966b": "DrawDetails(uint256)", "0x9bb5239a": "CheckPrize(address,uint256)", "0xff08d2b0": "PayMiners()", "0x9be1fcee": "BankOwner_DisableConnectBankAccountToNewOwnerAddress()", "0x5f972df8": "_jDiv(uint256,uint256,uint256,uint256)", "0xfe8b6642": "setEnforceRevisions(bytes32)", "0xe4cc1161": "seedWithGasLimit(uint256)", "0x5fd4b08a": "getName(address)", "0xaa51793c": "isLosingBet(uint256)", "0x31757f2e": "collisionCount()", "0xe1f5ebc5": "_projectAddNew(address,uint256)", "0x64228857": "getRevisionCount(bytes32)", "0x5ca3400c": "WithBeneficiary(address)", "0x39f4debc": "fillOrderAuto()", "0xcc2c5453": "add_sword(uint16)", "0x0a19b14a": "trade(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32,uint256)", "0x4d207d9a": "identify(address)", "0x3e83fe36": "getMyShares()", "0x8bb0faee": "setRef(string,string)", "0x38bbfa50": "__callback(bytes32,string,bytes)", "0xcee6f93c": "getResultOfLastFlip()", "0xbbba3333": "safer_ecrecover(bytes32,uint8,bytes32,bytes32)", "0x8e19899e": "withdraw(bytes32)", "0xd8389dc5": "hash(bytes32)", "0x8a4068dd": "transfer()", "0xea9e107a": "acceptRegistrarTransfer(bytes32,address,uint256)", "0x8f4ffcb1": "receiveApproval(address,uint256,address,bytes)", "0xf67abd87": "entryDetails(uint256)", "0x67acd805": "lowerMinWager(uint256)", "0xec035393": "_getAllRevisionBlockNumbers(bytes20)", "0xbd858288": "orderMatch(uint256,uint256,int256,uint256,uint256,address,uint8,bytes32,bytes32,int256)", "0x112c7075": "ManualDeposit()", "0xd81a91e9": "get_party2()", "0xc52bd836": "setDappOwner(bytes32,address)", "0xf84f420b": "getRandomNumber(address,uint256)", "0xcfe9a7b8": "getPackageName(uint256)", "0xe97dcb62": "enter()", "0x48db5f89": "player()", "0x6bdbf8e6": "concat()", "0x3c959aca": "CheckTickets()", "0x3aa5f4f7": "changeTokenSettings(uint16,uint256,uint256)", "0xac20902e": "NormalizeMoney()", "0x2fac1d36": "isReadyFor(address)", "0xdcaa5620": "findNextWeekday(uint256,bytes)", "0xf9909915": "bulkStoreHeader(bytes,int256,bytes,int256)", "0xcd2cdd5b": "claimOwnershi()", "0xcfae3217": "greet()", "0xf5c8d71b": "forceMove(address,address,uint256)", "0x9718b524": "newTreasury(address)", "0xd0679d34": "send(address,uint256)", "0x1301ee02": "transferringETC(address)", "0x60eb2826": "Badge()", "0x0d0c2008": "TwoAndAHalfPonzi()", "0x17e1bfb7": "addInstitution(address,string)", "0x06394c9b": "changeOperator(address)", "0x80c951bf": "currentClaimPriceInFinney()", "0xd063f55f": "toLittleEndian(uint64)", "0x53f818d6": "checkBetValue()", "0x9205fbc2": "testAuthorityAuth()", "0x3e4c0c82": "player_1(uint256)", "0xe571c35e": "ReverseRegistrar(address,bytes32)", "0x24804cef": "Deed()", "0x622e88cb": "testBitsXorSuccess()", "0xdfca2f53": "LookAtPrizes()", "0xafa293d4": "getSource()", "0x755f99c2": "AddNewSmallContract(address)", "0x7137ed47": "setProxyContract(address)", "0x835b42fc": "testThrowUpdateLatestRevisionNotUpdatable()", "0xdd34e129": "PriceTest()", "0xedb27f4e": "switchWizard(address)", "0x1c5d9faa": "setNickname(string)", "0x4746cef8": "_confirmAndCheck(address,bytes32)", "0x189c94ae": "testFallbackStaticSig()", "0x0cb749b6": "FutureBlockCall(address,uint256,uint8,address,bytes,bytes,uint256,uint256,uint16,uint256,uint256)", "0x2b25a7e4": "giveKudos(address,uint256)", "0x294f3d4d": "setUpLimit(uint256)", "0x2cce81aa": "getBlockHash(int256)", "0x4cd11943": "NewManualInvestor(address,uint256)", "0x7eaef50c": "over()", "0x8ac4e1d8": "TemperatureOracle()", "0xf108a7d2": "withdraw(uint256,address,string)", "0x00a676f9": "getExists(bytes32)", "0xb8d4efb5": "validate_percent(uint8)", "0xc7489441": "closeMarketMaker(uint256)", "0x3def449b": "FipsNotary()", "0x5687f2b8": "emitApproval(address,address,uint256)", "0xa9f8ec6c": "AlarmClockTipFaucet()", "0xd8e5c048": "scheduleCall(address,uint256,uint256)", "0x135217e7": "requires_depth()", "0x0aa46c12": "testClearBitFailIndexOOB()", "0x77d32e94": "ecrecovery(bytes32,bytes)", "0xace523c4": "createReferendum(string,string,uint256,uint256)", "0x5ca8bc52": "returnIt()", "0xdb318833": "_ecAdd(uint256,uint256,uint256,uint256,uint256,uint256)", "0x623195b0": "setABI(bytes32,uint256,bytes)", "0xd7bb99ba": "contribute()", "0x2880ebe7": "underdogPayoutMarkup()", "0x4ce01d86": "totalBetValue()", "0x837a7ba5": "testThrowTransferDisabled()", "0x386fcda8": "testCreateCostToken()", "0x0e850239": "scheduleCall(bytes4,bytes)", "0x163aba3c": "getQueryFee()", "0x9941e3d0": "setCallAddress(address)", "0x23637e60": "votePrice(uint256,bool)", "0xde78e78a": "tokenLaunched()", "0xe3579ea5": "publish(string,string,address,uint256)", "0x59a547b0": "recordCommission(uint256)", "0x1aa86370": "updateXIPFSPublicKey(string)", "0x97fcb54e": "transfer_eth(address,uint256)", "0x05d2f92a": "check_depth(address,uint256)", "0xdfcbb794": "TrustFund(address,uint256,address)", "0xb7dd1d17": "getAllRevisionBlockNumbers(bytes32)", "0x75862df4": "TokenWithEStop(address)", "0xd22057a9": "register(bytes32,address)", "0x29d017b5": "TestWithConstructor(address,uint256[])", "0xd216d55d": "etherandomExec(bytes32,bytes32,uint256)", "0xfba06849": "fipsPublishDataMulti(bytes20[],bytes)", "0xa37fd390": "setHomeAdv(uint256,string)", "0xcf2e3efc": "GetBankAccountBalance()", "0x423e7e79": "_dispatchEarnings()", "0x74087040": "testBitsNotEqualSuccess()", "0x61d585da": "state(bytes32)", "0xcfb3a493": "getMyBounty(uint256)", "0x5afeb106": "Sqrt()", "0xf9e84395": "unexempt(address)", "0x5669c94f": "issueToken(address,string)", "0x19b05f49": "accept(uint256)", "0x3ae01f84": "USDOracle()", "0x8c172fa2": "getEvent(bytes32)", "0x4671e65e": "proposeEmergencyWithdrawal(address)", "0xc27d7721": "create(uint256[101][])", "0x5c52e51e": "processPayout()", "0xf7a0fa0a": "getShareDistribution(bytes)", "0x31a3a506": "closeFunding()", "0x465e759b": "testRestart()", "0xb60d4288": "fund()", "0x52200a13": "getNumHolders(uint256)", "0xf2c298be": "register(string)", "0x7bc25372": "UserCheckBalance(address)", "0x104d5fdd": "getPriceProxy()", "0x447cd682": "scheduleTransaction(address,uint256)", "0xa045fdff": "scheduleCall(address,bytes)", "0x4757f1d2": "redeemAllOutcomes(uint256,uint256)", "0x5e855f14": "Dice(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x5d3c1d4c": "_getRequest(uint256)", "0x416c8701": "beyond()", "0x63aea3e0": "PlayerInfo(uint256)", "0xa163a624": "Test()", "0xedede601": "testBalance()", "0x13651124": "WithdrawAmountFromBankAccount(uint256)", "0x893d20e8": "getOwner()", "0x90b5561d": "insert(uint256)", "0xf9983a12": "GetMyInvestmentBalance()", "0xb71c47a2": "surrender()", "0xf2a75fe4": "empty()", "0x804ba97a": "tryGet(bytes)", "0x6506b623": "rotateBitsLeft(bytes,uint256)", "0x3ef8ec78": "announce_numbers(uint8,uint8,uint8,uint8,uint32,bytes32)", "0x73abecbb": "kill1()", "0xd5171523": "euroteambet()", "0x8e52cb51": "getRecordKey(bytes,bytes,bytes)", "0x7adbf973": "setOracle(address)", "0x4aa16737": "enter(uint8)", "0xf0cb556c": "updateLatestRevision(bytes32,bytes)", "0xbc8fbbf8": "nuke()", "0xc8e55708": "oraclize_query(string,string[1])", "0x7332b520": "getRewardsCount(uint256)", "0xf7c2b38c": "seconds_left()", "0xba344743": "_rawTransfer(address,address,uint256)", "0xcab5c0f1": "_incrementState()", "0xe044c2de": "newLoan(bytes,address,uint256,uint256,uint256,uint256,uint256,uint256)", "0x76abc03b": "getShareDistribution(uint256)", "0xf0da84f8": "getTransferable(bytes32)", "0xcde99727": "calculateROI()", "0x155dd5ee": "withdrawFunds(uint256)", "0x8b543b80": "maximumCredit(address)", "0x340ddda6": "MeatConversionCalculator(uint256,uint256)", "0x524f3889": "getPrice(string)", "0x84054d3d": "cashout()", "0x856f3080": "WhatWasMyHash(bytes32)", "0x0386a016": "closeProposal(uint256)", "0xcebce72d": "token(uint64)", "0x7f480f9d": "processDividends(address)", "0x11d12402": "testEasyPropose()", "0x2f695053": "getCertifierAtIndex(uint256)", "0xd9fcb31f": "comm_channel()", "0x141c4e60": "challenge(uint256,address)", "0x4ff13571": "x2()", "0xa01bc729": "monster_attack(uint256)", "0x2fe9541f": "addIssueBounty(string,uint256)", "0x5503a659": "smallponzi()", "0xdfc765dc": "getMatchers_by_index(uint256)", "0x0b7623ba": "abs(int8)", "0xcde0a4f8": "setRegulator(address)", "0xf95b5a58": "getInitialAnswer(uint256)", "0x66b42dcb": "register(address,string,uint256,string)", "0x9f2ce678": "vote(bytes32,bool)", "0xb3559460": "getGenerationSize(uint256)", "0x5ddae283": "transferRegistrars(bytes32)", "0x59dc735c": "getClient()", "0xc258ff74": "List()", "0x4fb4bcec": "step5()", "0xed684cc6": "trigger(uint256)", "0x09405164": "getOpenCandidates()", "0x5c5d625e": "getProof()", "0x9f5f7c7f": "tokenSplit(address,address,address,uint256)", "0x0e38901a": "unvault(uint256)", "0x75160a20": "pay_royalties()", "0x15398afe": "compareNumericStrings(string,string)", "0xbbd8b602": "getOracleOutcomes(bytes,address[])", "0xebae35a6": "DAOTokenCreationProxyTransferer(address,address)", "0x15abc160": "createValidatedRequest(address[3],address,uint256[11],uint256,bytes)", "0x830953ab": "claimAmount()", "0x26b916b4": "Set_Interest_Rate(uint256)", "0x1fb291cb": "registerInt(address,int256)", "0x505fb46c": "add(uint256,uint256,uint256)", "0xf00d4b5d": "changeOwner(address,address)", "0x034187fd": "setEthToCents(uint256)", "0x94d9cf8f": "CreateProxyWithControllerAndRecovery(address,address[],uint256,uint256)", "0xacbf98a7": "endsWith()", "0xfc2c3e08": "getIteration()", "0x6d7da0b1": "MyContract()", "0x1558ae4d": "Etheroll()", "0x42cbb15c": "getBlockNumber()", "0x29cd62ea": "setPubkey(bytes32,bytes32,bytes32)", "0x2030f721": "num_objects()", "0xbc08afd9": "WebOfTrustToken(address,uint256)", "0x8cdfb1e6": "transferIfHF(address)", "0xa0bd3c0f": "scheduleCall(address,bytes,bytes,uint256)", "0x4e71e0c8": "claimOwnership()", "0xc1cc0775": "calculateFeeDynamic(uint256,uint256)", "0x50c42921": "replicate()", "0x25495998": "getMinimumConsumerDeposit()", "0x3d8e2947": "getFileAddress(bytes)", "0x1f794436": "getBlockHeader(int256)", "0x7d380265": "addOptionChain(uint256,string,uint256,uint256,bytes32,address,int256[])", "0xec0b4153": "getMoneyness(int256,uint256,uint256)", "0x01775f23": "_closeBooks()", "0x9d063ed8": "FIFSRegistrar(address,bytes32)", "0x083b2732": "callback()", "0xa1920586": "offer(uint256,uint256)", "0x19c47214": "getBlockVersion(bytes)", "0xa293d1e8": "safeSub(uint256,uint256)", "0xfe73e3ec": "preliminaryGameResult(uint64)", "0xf004b12b": "CrowdFund(uint256,uint256,address)", "0x54d03b5c": "changeFeeMake(uint256)", "0x9dbc4f9b": "participantDetails(uint256)", "0xd002462b": "setDeploymentFee(uint256)", "0xed2b8e0b": "getPoolRotationDelay()", "0xf697a0ed": "ppb(uint256,uint256)", "0x964c836c": "receiveExecutionNotification()", "0x5e0e2957": "dumpOut()", "0x33232609": "blake2b(uint64[],uint64[],uint64)", "0x88f53db1": "getDataRequest(uint256)", "0x0caf9d39": "testFailTooManyMembers()", "0x1f2e886c": "testControllerTransferTriggersEvent()", "0x586a69fa": "getMaximumStackCheck()", "0xf64fca2e": "getNodeId(bytes)", "0x8b95ec0c": "testAddBalance()", "0x32e7c5bf": "B()", "0x57e6c2f4": "isAuthorized()", "0xb2f2588b": "sortNumbers(uint8[3])", "0xe95bee59": "checkFormat(string)", "0xcef887b0": "storeBlockWithFee(bytes,int256)", "0xbe26733c": "Kill()", "0xe82f7dd4": "testThrowsRetractLatestRevisionNotUpdatable()", "0xfd7ac203": "TestToken()", "0x6d052f56": "testBitsSetSuccess()", "0xf65c4d42": "Participate(uint256)", "0x432c685f": "trustClient(address)", "0xb0171fa4": "getCurrentGenerationId()", "0x03251a08": "setMin(uint256,uint256)", "0x58d3b617": "Notifier(string)", "0x15dacbea": "transferFrom(address,address,address,uint256)", "0x83f7b8e1": "getNumberOfPhotos()", "0xf1076703": "getVerificationId(address,bytes,bytes)", "0x752a3df6": "transferIfHardForked(address)", "0xaf93afdd": "Shipment(bytes,bytes,bytes,bytes,string,bytes,uint256,uint256,bytes,bytes,uint256,uint256,string,bytes,bytes,bytes)", "0x7fcf532c": "Withdrawal(address,uint256)", "0x72ea4b8c": "getNumInvestors()", "0x7df52ba8": "Arbitrate(uint32,uint32,bool)", "0xea25f24a": "TokenCreation(uint256,uint256,address)", "0xf74100e3": "getBits(bytes)", "0x63bfe3d8": "SkillBeatsLuck()", "0x80599e4b": "remove(string)", "0x6c050eae": "look()", "0xf9a794ad": "EtherLovers()", "0x501e8428": "getPart(bytes,uint256)", "0xf446c1d0": "A()", "0x2d67bb91": "World()", "0xeef547d7": "deal_details(uint32)", "0xa6cb9e64": "scheduleCall(address,bytes,bytes)", "0x659010e7": "m_spentToday()", "0x9b0b9c07": "acceptBankDraft()", "0x315e2f1b": "setTestString(string)", "0x69bcdb7d": "getCommitment(uint256)", "0xe1376da2": "updateFirstActiveGamble(uint256)", "0xc70d169d": "answerRequest(uint256,bytes)", "0xa094a031": "isReady()", "0x74e60a48": "cancelOrder(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32)", "0x1b5ee6ae": "mintToken(int256,address,uint256)", "0x7f791833": "toTimestamp(uint16,uint8,uint8,uint8)", "0x5e6ad49d": "_setCosignerAddress(address)", "0xb2e85b67": "getPlayerStatus(address,uint256)", "0x30a24abd": "create(bytes4,bytes)", "0x9f0e3107": "get_timestamp(bytes32)", "0x33397816": "withdrawAccountBalance(address)", "0x0da3e613": "EthFactory()", "0xa5f3c23b": "add(int256,int256)", "0xc1829a14": "testFailTooFewConfirms()", "0x5322f0c5": "getChannelOwner(bytes)", "0x0fd1f94e": "firstClaimBlock()", "0x639d57f2": "testGetBitSuccess()", "0xd3aa22c7": "transferTLA(string,address)", "0x86314af9": "BetOnHashV84()", "0x3059ac30": "Escrow(address,address)", "0x0efafd01": "getPlayerGainLossOnLastFlip()", "0x49bf66d3": "addRegistryIntoNameIndex(address)", "0x3af39c21": "undefined()", "0xb660d77c": "switchMPO(address,address)", "0x0fdb468f": "fee(uint64)", "0x72479140": "CreateTicket(address,uint8,uint8,uint8)", "0xe79a198f": "unregister()", "0x688dcfd7": "setProofType(bytes1)", "0xfb9a4595": "GitHubBounty()", "0xd02a9889": "getDateOfFirstPayment()", "0xca35271c": "numDebtors(address)", "0x08714bfa": "TestContract()", "0x16d960b5": "createThing(bytes32[],bytes32[],uint88)", "0x17961d0f": "ord()", "0x62ba9687": "toTimestamp(uint16,uint8,uint8,uint8,uint8)", "0xbba91ea7": "getHomeadvIndex(uint256)", "0xb45105b2": "post(string,address,string)", "0xa7e25683": "testShortOutput()", "0xa068e8d3": "convict(uint256,uint256,uint256,uint256)", "0x09a69f57": "getRewardAmount()", "0xe7334156": "processNextDeposit(address)", "0x62ee6d29": "changeHashtoLowOrHigh(uint256)", "0xa80d4e9a": "EtherAuction(uint256)", "0x18489f50": "thingExist(bytes32[])", "0x5323c6cf": "calcCostsBuying(bytes,uint256,uint256[],uint8,uint256)", "0x9a777d5d": "buyCoins()", "0x36344022": "testAuthorizedTransfer()", "0x8b863095": "setContractorProposal(uint256,bytes)", "0xd10e99fe": "mint(int256,bytes32)", "0x12c82bcc": "sendRobust(address,uint256)", "0x0bf75567": "voteSuperQuorum(uint256,bool)", "0xf5f6ea26": "EthOne()", "0x14918f5e": "performInitialWithdrawal()", "0xdf32754b": "owned()", "0x1632070c": "setRewardDivisor(uint256)", "0xe50a3bb1": "oraclize_query(string,string[],uint256)", "0x9b619d3b": "_deleteAllPackedRevisionBlockNumbers(bytes32)", "0x3b751f7f": "claimThroneRP(string)", "0xd55ec697": "upgrade()", "0x43703b0e": "getEventData(bytes)", "0xd1bf9aba": "nextRune()", "0x76849376": "addNode(bytes32,address)", "0xfd958695": "isAlphaNumeric(bytes1)", "0x9fa5e5d5": "setARKowner(address)", "0x6e2cde85": "drawPot(string,string)", "0x4d5b080c": "scheduleTransaction(uint256,address,uint256)", "0xf1e4a540": "unsetCoordinator()", "0x364f4896": "emission(address,address,uint256,uint16,uint16)", "0x9183fd01": "getSeedPrice()", "0x9832ee65": "resultsWeightedByTokens()", "0xdd5244b4": "testTryProxyCallWithValue()", "0xec2ec781": "testFailGetUnsetToken()", "0x7db9743b": "Registry()", "0x1adf2d1a": "Offer(address,address,bytes,uint256,uint256,uint128,uint256)", "0x1ba326c4": "calcShare(uint256,uint256,uint256)", "0x3ced842b": "make_offer()", "0xea295ec2": "calcRevenue(address)", "0x3c894475": "scheduleTransaction(address,bytes,uint8,uint256[6],uint256)", "0x477bddaa": "setContractAddress(address)", "0xed180443": "getUint256(int256)", "0xee725d44": "toChannelID(string)", "0xa4898fd5": "deployContract(address)", "0x75ee85bd": "salsa20_8(uint256,uint256)", "0xbe7c29c1": "getNewDAOAddress(uint256)", "0xfe01f1ff": "TokenTester()", "0x6103d915": "Winners(uint256)", "0xa30b5c69": "AttributeModel()", "0x81064e2d": "getCreditorAmounts()", "0x23584a21": "initStats(string,address,uint256)", "0xe2894a8a": "OwnerAnnounce(string)", "0xa84c5330": "createNewRevision(bytes20,bytes)", "0xb742398b": "trade(address,uint256,bytes,address,uint256,bytes)", "0x3211bb90": "OwnerAddFunds()", "0xa0befa94": "getStake(uint256,uint256)", "0x6dc3edcf": "executeExecutable(uint256,uint256)", "0x17e875e3": "Transparancy()", "0x6939864b": "lotteryState()", "0x3b107682": "DualIndex()", "0x51b3d7b9": "_transferWithReference(address,uint256,string)", "0xdf7cec28": "cancelBid(bytes32)", "0x1e74a2d3": "getMinimumEndowment()", "0x39b333d9": "Play(uint8,uint8,uint8,uint8)", "0xcbd08c8c": "config(uint256,uint256,uint256,uint256)", "0xca6ad1e4": "setCustomGasPrice(uint256)", "0x510f44cb": "TestFactoryUser()", "0xcee6ee38": "aEthereumlotteryNet()", "0x11610c25": "bet()", "0xb73405a9": "roundMoneyDownNicely(uint256)", "0xb8851fea": "endDateStart()", "0xa4325485": "getCreatorBalance()", "0x2c181929": "getChainWork()", "0xffb4c857": "_confirmAndCheck(bytes32)", "0x9e66cd38": "free(uint64)", "0x44e43cb8": "depositRevenue()", "0xa553a597": "configure(uint256,uint256,uint8,address)", "0xc47f0027": "setName(string)", "0x565a2ecf": "classicTransfer(address)", "0x1da0fb1b": "updateSettings(uint256,uint256,uint256,uint256,uint256,bool)", "0xa5ebf389": "getMoneyTotals()", "0x7f445c24": "subRegistrar(string)", "0x9ad4f98e": "BlocksureInfo()", "0xd6b4ec12": "getDailyWithdrawalLimit()", "0xe0886f90": "at(uint256)", "0x5dc77e26": "andThen(string,address)", "0xe7d50e5c": "FarmShare()", "0x3f2965f0": "registerSeller(address)", "0x85b73d3c": "testCreateNewRevision()", "0x49437210": "getUpdatable(bytes32)", "0xe1a9109d": "setSeedPrice(uint256)", "0x95978868": "strConcat(string,string,string,string,string)", "0x511b1df9": "addr(string)", "0xc5b1d9aa": "newRound()", "0xecf6eb22": "setConfigAddress(bytes,address)", "0x9a9c9c53": "DepositToBankAccount()", "0x27ea6f2b": "setLimit(uint256)", "0xd2dc0869": "add(string,uint256,string,string,address)", "0xc86a90fe": "sendCoin(uint256,address)", "0x5dfc2e4a": "noop()", "0xd81e8423": "get(address,address)", "0x4cad42d3": "testWager()", "0xd120a284": "getBytesFromNumbers(uint8[3])", "0x991ffd4e": "scheduleCall(address,bytes,bytes,uint256,uint256,uint8,uint256)", "0x60c311fd": "doBurnFromContract(address,uint256)", "0xbb6a0853": "GreedPit()", "0xc27b2c2d": "collectEarnings()", "0x446294ad": "multiAccessGetOwners()", "0x5bec9e67": "infinite()", "0x47e7ef24": "deposit(address,uint256)", "0x8d216186": "roll(uint256,bytes32)", "0xdf300b46": "getThing(bytes32[])", "0x5a6c787e": "updateWithMPO()", "0x1f2dc5ef": "divisor()", "0x421aeda6": "Set_your_game_number(string)", "0xba1162d7": "getFmLength()", "0x853255cc": "sum()", "0x20768ee8": "getProposalID()", "0xf5c57382": "nameOf(address)", "0x4e417a98": "callData()", "0xc90d080a": "registerEvent(bytes)", "0x0b1e400a": "_transferFromToICAPWithReference(address,bytes32,uint256,string)", "0xe420264a": "g(uint256)", "0x8a00a82f": "withdrawRewardFor(address)", "0x06638e92": "GetNumbersFromHash(bytes32)", "0xf63da25b": "Emailer()", "0xc01706dd": "getContentByRank(address,uint256,uint256)", "0xe1108706": "rfind()", "0xffd10e07": "enterPool(address)", "0xc4254c7b": "CoreWallet()", "0x30e0789e": "_transfer(address,address,uint256)", "0x992ae976": "isSafePunctuation(bytes1)", "0x4afce471": "test_requires_depth(uint16)", "0xda7fc24f": "setBackend(address)", "0xeb7402f5": "multiAccessHasConfirmed(bytes32,address)", "0x6c1a5b8c": "TOKEN_TARGET()", "0xb3a2a999": "nextWithdrawal(bytes16)", "0x4a420138": "scheduleHeartbeat()", "0xb1233451": "setTerm(uint256,string)", "0x266710ca": "manualUpdateBalances_only_Dev()", "0x98866c1a": "personUpdateDOD(uint256,int256)", "0xaa6be303": "debtors(address)", "0xda5c0a7c": "testDisown()", "0x8757a2cd": "test_depth(uint256,uint256)", "0xe1bedf2a": "AlarmTester(address)", "0x5dcbac7a": "registerBytes(address,bytes)", "0xa587da29": "setPackage(bytes,uint8,uint8,uint8,bytes)", "0xc3da42b8": "c()", "0x4b8772c1": "buyUnit(uint256,uint256)", "0x67f12ecf": "validate(address,uint256,uint256[101][])", "0x0bd089ab": "MyAdvancedToken(uint256,string,uint8,string,address)", "0x32829a23": "OpenBankAccount()", "0xdea06188": "NumberOfBlockAlreadyMined()", "0x61e539da": "testFailWrongAccountTransfers()", "0x8a3e44d4": "assetMoveInformation(address,address)", "0x1327d3d8": "setValidator(address)", "0x207c64fb": "validate(address)", "0x80acaafb": "profitDistribution()", "0x90b98a11": "sendCoin(address,uint256)", "0x8b147245": "update(bytes32)", "0x920c94df": "BuyTicketForOther(address,uint8,uint8,uint8)", "0x6f698fb5": "setMinimumQuorum(uint256)", "0xac5e81a9": "historyPayout(address)", "0x70d084c0": "SingularDTVCrowdfunding()", "0x67ce940d": "getOverhead()", "0x7a791524": "setNextFeePercentage(uint8)", "0xd6d902c4": "claimThroneFor(bytes,address)", "0xc71e48d6": "setOutcome(bytes32,bytes32[])", "0xef41e06f": "testThrowSetEnforceRevisionsNotOwner()", "0x70be4ffa": "testErrorUnauthorizedSetPackage()", "0x6d12301c": "getBetValue(bytes32,uint8)", "0x4e69d560": "getStatus()", "0x55234ec0": "remaining()", "0x5cfd8c24": "ResetPonzi()", "0xe29fb547": "scheduleCall(bytes4,uint256,uint256,uint8,uint256)", "0xbd119967": "add_rating(uint256,uint256)", "0x966acb38": "testThrowTransferNotTransferable()", "0x5c665f89": "getFunds(address,bool)", "0xa59d6986": "recoverLostFunds()", "0x403abbc7": "updateFirstActiveGamble()", "0x0230a07c": "releaseDeed(bytes32)", "0x2d116186": "deityBalance()", "0x602acca1": "InchainICO(address[],uint256)", "0xd393c871": "register(string,address,uint256)", "0xe0fe075e": "payoutReady()", "0xf3e3c629": "testBalanceOfStartsAtZero()", "0x48cd4cb1": "startBlock()", "0x669459a7": "removeRegistryFromOwnerIndex(address)", "0x74d89c47": "testUpdateNameDb()", "0x182db370": "getWhatHappened()", "0xf363441f": "getCreatorDotBalance()", "0x6896fabf": "getAccountBalance()", "0xb29a0308": "logAnonymous(bytes,bytes,bytes,uint256)", "0x3023d0c4": "Ethstick()", "0x1077f06c": "makeClaim(uint256)", "0x8a519fb9": "BlockChainEnterprise()", "0xbffbe61c": "node(address)", "0xe20bbd8d": "RecoveryWithTenant()", "0x03bda14e": "raiseMaxNumBets(uint256)", "0xe1041d86": "__throw()", "0x3d79d1c8": "bal()", "0xbd3f0965": "AiraEtherFunds(string,string)", "0xc388cca6": "testBitAndFailIndexOOB()", "0x921f98bb": "resolveFailVote()", "0x4bbb216c": "_target(address)", "0x10922cc1": "testTransferCost()", "0xeceb2945": "checkProposalCode(uint256,address,uint256,bytes)", "0x3df4ddf4": "first()", "0x21a49ec2": "LCoin()", "0x6d1f00a6": "ThroneMaker(uint256)", "0xcaed4f9f": "DataService()", "0x5cff876b": "carrotsCaught()", "0x3a7fb796": "mintGreen(int256,address,uint256)", "0x370b6939": "AdminSetDrawer(address)", "0x2d9a37d3": "getMaxPayout()", "0x739f888c": "setNewEstimate(int256,int256)", "0x04dd69fa": "getGenerationIdForCall(address)", "0xb764e273": "failSend()", "0xd4dfadbf": "getMarket(address)", "0xa0e67e2b": "getOwners()", "0x2d592a34": "sellKissBTC(uint256)", "0x8a341c83": "testErrorRootAuthorityChangeUnownedPackage()", "0xfcc6b5d5": "fillTheirOrder(address)", "0x61649472": "getPoolFreezePeriod()", "0x3dd297da": "safeMultiply(uint256,uint256)", "0x5d3278f0": "LooneyFifty()", "0x7399646a": "theRun()", "0x3e450fff": "adminDeleteAccount()", "0x93cc9162": "taskRejected(uint256,uint256)", "0xbc5d0f65": "beginExecution()", "0x1934d55a": "isPermanentlyApproved(address,address)", "0xf1cff4b5": "testBitsNotSetSuccess()", "0xf240f7c3": "dispute()", "0xf6d5959b": "getActionStatus(uint256)", "0x0f23cbaa": "recycle()", "0x74f8d96e": "getRevisionBlockNumber(bytes20,uint256)", "0x0bad342a": "EscrowContract(address,address,address,address,uint256,uint256,uint256,uint256)", "0xa6afd5fd": "getBets()", "0x84ad6ff3": "ReversibleDemo()", "0x62b3b833": "createCoupon(string)", "0x523ccfa8": "isKnownCall(address)", "0xacc8cb18": "pushTerm(string)", "0xa753d6f2": "CreateProposal(string,string,string,string,string,string,uint32,uint32)", "0xc5699d68": "_compare(int256,bytes,int256)", "0xe2faf044": "createDAO(address,uint256,uint256,uint256)", "0xa15afb48": "Replicator()", "0x352d2790": "UUID4()", "0x7dee2cad": "CancelMyInvestment()", "0xbeabacc8": "transfer(address,address,uint256)", "0x674f220f": "previousOwner()", "0x4b3b6168": "SetNewBigContract(address)", "0x54ba7daa": "enter(bytes,bytes)", "0x06fdde03": "name()", "0x5944427b": "getRequestResult(uint256)", "0x983ef725": "getDifficulty(uint256)", "0x9287c877": "getNavLength()", "0xa987d654": "restoreItem(uint256)", "0x05433a26": "GetNumbersFromHash(bytes)", "0xd04bfc9c": "buyer_pay()", "0x4a9b3f95": "personUpdateName(uint256,string)", "0xe6b972f5": "userName(address)", "0x88782386": "UnicornMilk()", "0x74580e2f": "changeCreator(address)", "0x1785f53c": "removeAdmin(address)", "0xad544dcb": "testSetNotUpdatable()", "0xafd09bab": "quadrupler()", "0x77bc222c": "_eraseSingleNode(bytes32)", "0x09957e69": "newSale(bytes,uint256,uint256)", "0xa21931ea": "CreateProposal(string,string,string,uint32,string,string,string,uint32,uint32)", "0xbb6b4619": "SendETC(address)", "0x3aa94b1d": "getCoinStats(uint256)", "0x9e2262f5": "testCreateCostData()", "0x2bf4e53d": "getCurrentShareholders()", "0x6111dd02": "calcCostsSelling(uint256,uint8,uint8,uint256)", "0x6b9b1006": "TransactionRecorder()", "0x83b23b40": "cEthereumlotteryNet()", "0x770c6cbb": "WithDrawPreForkChildDAO()", "0x67080f6e": "testThrowsSetEnforceRevisionsNotOwner()", "0xa10bee85": "_transferFromWithReference(address,address,uint256,string)", "0x49cc954b": "twoYearsPassed()", "0x88c3ba85": "ParallelGambling()", "0x03985426": "getMode(bytes32)", "0xad8ed335": "__proxy(address)", "0x306387a4": "dealStatus(uint256)", "0x0343dfa0": "checkInvariants()", "0x23df9df5": "_refund(uint256)", "0x837e7cc6": "rollDice()", "0x98b1e06a": "deposit(bytes)", "0xa0440426": "purchaseProduct(uint256,uint256)", "0x4cb71b9b": "getAllReleaseHashes()", "0x3a7d280c": "login(string)", "0xb9a904f9": "testUnauthorizedSetBetaPackage()", "0xe94a4db1": "isSuitableGen(uint256,uint256)", "0x60f8af90": "refundRound()", "0x43bf718e": "getHashOfTheProposalDocument()", "0x4a30f976": "censorship(uint256,bool,bool)", "0x47e46806": "toString()", "0x8d59cc02": "register(address,string,string)", "0xb3fb14ad": "getGameResult()", "0x4a23dc52": "FileStore()", "0x8da5cb5b": "owner()", "0x3cc8daf7": "setNameOwner(bytes,address)", "0x14cbdb54": "EspCoin()", "0xc47cf5de": "getAddress(bytes)", "0x71e11354": "updateRegistration(string,string)", "0x8b2e6dcf": "publish(bytes32)", "0x12d00c2e": "soloWithdraw(uint256)", "0xd68199dc": "gameStats()", "0xf0f967e8": "canCall(address,address,bytes)", "0xe0c7c117": "Randao()", "0xddeae033": "claimFor(address)", "0xcec7260b": "move_monster(uint16,uint16)", "0xe51ace16": "record(string)", "0x4f76cb02": "testGetBitFailIndexOOB()", "0x942385eb": "getPayroll()", "0x46d667db": "setBytes32(bytes)", "0x35a063b4": "abort()", "0xb1d51d31": "pay(uint64,address)", "0x7140bdf3": "get_all_best_offers()", "0x0380e2f3": "getHashOfTheSignedDocument()", "0x2feda2fa": "POI()", "0x3c0870ae": "challenge(uint256,uint256,uint256,bool)", "0x27a5c7c6": "voteDecline(uint256)", "0xafd8c8c4": "GasProxy(address,address)", "0x8de93222": "purchase(address,uint256)", "0x087e055a": "getConfigBool(bytes)", "0xbaccc92b": "RegulatorIfc(address)", "0x8c546f81": "GNT()", "0x57cb2fc4": "getInt8()", "0xe3083fb5": "removeFromContribution(uint256)", "0x1a092541": "getDescription()", "0x7486a8e3": "get_publisher(bytes32)", "0x089e0ad0": "buildDSMap()", "0x29161820": "Base(uint256)", "0xe2f8feb2": "internal_tester(int256)", "0x1d2dbb22": "CancelMyInvest()", "0x726ab4ef": "getParentHash(bytes)", "0x83d8a90f": "theDonkeyKing()", "0x9babdad6": "removeShareholder(address)", "0xdeb931a2": "getOwner(bytes32)", "0x90cb04e1": "buy(string,uint256,uint16)", "0x2ff92323": "oraclize_query(uint256,string,string[4])", "0xf91a792e": "decryptHand(string,uint256,uint256,uint256)", "0xebf6e91d": "hit(uint256)", "0xb085b9a5": "Example()", "0x07b2779f": "BasicRegulator(address,uint256,uint256)", "0xe10e5dce": "_build(bytes)", "0x3e239e1a": "getHour(uint256)", "0xacc5a0dc": "GetPrize()", "0xa79deb4f": "acceptTradeDeal()", "0xd7c26adb": "oraclize_setProof(bytes1)", "0xc6a17d2b": "pow10(uint256,uint8)", "0xa87d942c": "getCount()", "0xe706918c": "testToggleBitSuccess()", "0xc1812b15": "reorganizeOwners()", "0x7c7c7695": "getAccountID(address)", "0x1a26ed1c": "validateReservedWindowSize(uint256,uint256)", "0x87393bc6": "verifyFirstHalf(uint256[4],uint256[4])", "0x2e52d606": "n()", "0x2037fcbf": "withdrawInvestment(uint256)", "0x77228659": "query2(uint256,string,string,string)", "0xf67a1d37": "BlockChainChallenge()", "0xc67146a5": "check_bet(uint256,address,uint8)", "0xc89f2ce4": "funds()", "0x58e29e17": "initiateProof()", "0xf0e10c0d": "play(address,uint256)", "0x480b70bd": "scheduleCall(address,bytes4,uint256,uint256)", "0x5294157f": "sendWithAllOurGasExceptExt(address,uint256,uint256)", "0xad447a19": "getBalanceDB()", "0x41095b60": "voteForUltimateOutcome(bytes,uint16)", "0x5521d17b": "betOnColor(bool)", "0xc8f8d75d": "Config(uint8,address)", "0x10ae4ce2": "setReleaseValidator(address)", "0x9fd4f7d1": "replaceWizard(address)", "0x77c78df9": "getCurrentLevel()", "0x1dda5c7d": "testFailSubBalanceBelowZero()", "0x21e5383a": "addBalance(address,uint256)", "0xb0414a2d": "setMinimumGasLimit(uint256)", "0x919840ad": "check()", "0xf651bf44": "move_to(uint16)", "0x0b97bc86": "startDate()", "0x29f1bff4": "withdrawFromChildDAO(uint256)", "0x7a02dc06": "getInfo(bytes32)", "0xe5bf93b9": "balanceEther(uint256)", "0x288c6ed2": "getSeedCost(uint256)", "0x4db3da83": "scheduleCall(bytes4)", "0x270cfee1": "getTokenAccount()", "0xb5d03751": "YoutubeViews()", "0x27e056a5": "addMinter(int256,address)", "0xfa7d68f1": "getAccountInfo(uint256,uint256)", "0x09fc8f6d": "isTokenUpgraded(bytes32)", "0xa5b9e922": "getContentTimetamp(uint256)", "0x1f8947c1": "extractUint(int256,bytes,uint256,uint256)", "0xbcc941b6": "totalWinners()", "0x1db71ffb": "doLoops(uint256)", "0x35ae41c9": "godAutomaticCollectFee()", "0x42cf0e72": "searchByOwner(address)", "0x69f18967": "testSetBitFailIndexOOB()", "0x57b07cd9": "getReleaseHash(uint256)", "0x2baf4f22": "_safeFalse()", "0x3133f2a7": "outstandingBalance()", "0x773c84ee": "exec(address,bytes,uint256,uint256)", "0x9070b18d": "_getAllRevisionBlockNumbers(bytes32)", "0x476e04c7": "NewMessage(string)", "0x3b91ceef": "setMax(uint256,uint256)", "0xe6cb9013": "safeAdd(uint256,uint256)", "0xc2038560": "setOutcome(bytes,bytes)", "0xbed34bba": "compareStrings(string,string)", "0xba8661a2": "TimestampScheduler(address)", "0x4c4aea87": "getReleaseData(bytes32)", "0x1177892f": "getBalanceByAdress(address)", "0x126a710e": "dnsrr(bytes32)", "0x60913244": "botOnSale(uint256,uint256)", "0x5731f357": "oraclize_query(uint256,string,string,string)", "0x3e58c58c": "send(address)", "0x2187a833": "setGreenToken()", "0x5f09952e": "voteAllowTransactions(bool)", "0xf2b26d8f": "nextEtherForSale()", "0x1f201e39": "etherandomExecWithGasLimit(bytes32,bytes32,uint256,uint256)", "0x43d726d6": "close()", "0x6cdf4c90": "ownerSetMinBet(uint256)", "0xe6e8c692": "computeResponseFirstHalf(uint256,uint16)", "0xd1d80fdf": "setAddr(address)", "0x6da1833c": "getInstitutionByName(string)", "0x7682e6ff": "getTrustSetting(address)", "0x8f6f988c": "setUltimateOutcome(bytes)", "0xe299beb3": "SimpleIndex()", "0xa2bb5d48": "get_username(address)", "0x780900dc": "create(uint256)", "0x78710d37": "seven()", "0x2b20e397": "registrar()", "0x4094ef5e": "addDataRequest(string)", "0xc630f92b": "canEnterPool()", "0xdd114c22": "publish(address,uint256,address,uint256)", "0x57006864": "checkBetParity(uint8)", "0x45788ce2": "prev(address)", "0xee8ff562": "setMaxProfit()", "0xdc63a62c": "getFileListHead()", "0x9447fd0a": "until()", "0xb303dcbd": "Owned()", "0x0ecaea73": "create(address,uint256)", "0x20339891": "addGridMember(address)", "0xc8c01a55": "request(address,uint256)", "0x76f10ad0": "getSnapshot(uint256)", "0xc06c4474": "get_burned(bytes32)", "0x67cb61b6": "getChoice()", "0xca708230": "funnel()", "0x08b7fa31": "PriceFeed()", "0xac6bc853": "startSpin()", "0xf4bbfd6a": "scheduleCall(bytes,bytes)", "0xab73e316": "next(address)", "0xba0179b5": "confirm(uint256)", "0x1e62be25": "Bytes32Passer()", "0xb950556a": "setThingValid(bytes32[],bool)", "0xb61c0503": "fireEventLog1()", "0x79a85e6c": "getProductInfo(uint256)", "0x959ac484": "push(uint256)", "0x78e80b39": "UserGetPrize()", "0xff74927b": "strConcat(string,string)", "0xd207e757": "ownerSetOraclizeSafeGas(uint32)", "0x5819dde2": "getNumbersFromBytes(bytes3)", "0xf34ed4e6": "RanDAOPlus(address)", "0x3943807b": "insert(bytes,bytes,int256)", "0x38cc4831": "getAddress()", "0x12a7b914": "getBool()", "0xa4fd6f56": "isEnded()", "0x6c86888b": "testTrade(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32,uint256,address)", "0x0bebd0f9": "addAddressToGeneration(address,uint256)", "0x003074ff": "getFrontend()", "0x2776a859": "computeResponseSecondHalf(uint16)", "0xadfe6b80": "InvestAdd()", "0x6981b5f4": "getLength(string)", "0x4a994eef": "setDelegate(address,bool)", "0xbac1e9f6": "getChannelSize(address,uint256)", "0xbdfdb519": "accept(string,uint256,uint16)", "0x68f5aa0f": "setShareholderDB(address)", "0xafb95eed": "logApproval(address,address,bytes32)", "0xae152cf4": "oraclize_query(string,string,uint256)", "0xe3b26a8c": "SocialNetwork()", "0x54204ad4": "triple()", "0xbc8f3bcb": "ZeroDollarHomePage()", "0xf0350c04": "transfer_ownership(address)", "0xfde9ba41": "transfer(bytes,address,uint256)", "0xfa2acd87": "G(uint64[16],uint256,uint256,uint256,uint256,uint64,uint64)", "0x3a76abff": "_eraseNode(uint256,bytes32[],bytes32)", "0x0acf473b": "AdminCloseContract()", "0x299e7318": "resolveVoting()", "0xb414d4b6": "frozenAccount(address)", "0x8d375da2": "testMakeItFail()", "0x6e8dad74": "retrieveAccountBalance(bytes,bytes)", "0xd3aa831f": "testOwnedTryAuth()", "0x13220305": "doTransferOther(address,address,address,uint256)", "0xb0c7f709": "kingAutomaticCollectFee()", "0x2dabbeed": "reclaim(uint256)", "0x5c19a95c": "delegate(address)", "0xbc2a4dd6": "doBalanceOf(address)", "0x8e7ea5b2": "getWinner()", "0xdb37e42f": "multisetProofType(uint256[],address[])", "0xa6b197aa": "Order(address,uint256)", "0x8cf4dbfb": "collectBalance()", "0xd0821b0e": "bet(uint8)", "0xa02b161e": "unregister(uint256)", "0x09dd0e81": "getBlockchainHead()", "0x8a0807b7": "indexOf(string,string)", "0xe3914699": "dEthereumlotteryNetWinners(address)", "0x44d75fa9": "updateMinorTree(bytes32)", "0x3c21db0a": "theGames(uint256)", "0xf0e959f9": "TokenSales(address)", "0x696bda86": "submitProposal(uint256,bytes)", "0x3b343a13": "getNodeAddress(bytes)", "0x2812f8b8": "FutureCall(address,uint256,uint16,address,bytes4,bytes,uint256,uint256,uint256)", "0xbf32bf97": "FailGuyTax()", "0x89ced196": "setNotUpdatable(bytes32)", "0xb94e962a": "allocateTickets(uint256)", "0x7a479160": "getRequestArgs(uint256)", "0x5a825cbb": "getPayment(uint256,uint256)", "0x4ca8b0d0": "registerExistingThrone(bytes,address,uint256,uint256)", "0x82afd23b": "isActive(uint256)", "0x6ebf10fe": "storeHeader(bytes,address)", "0x1437f9a3": "Set_your_game_number(uint16)", "0xd98d011d": "getCandidateKey(bytes,bytes,bytes,bytes)", "0x8b676ae8": "scheduleCall(address,bytes4,uint256,uint256,uint8,uint256,uint256)", "0x90fd53ec": "farmTile(uint8,uint8,int8)", "0x46e44f63": "getCheckRecordTS(bytes)", "0x1de38038": "makercoin(uint256)", "0xc038a38e": "totals()", "0xfa80918b": "computeNodeId(bytes,bytes)", "0xc76a4bfb": "relayReceiveApproval(address,address,uint256,bytes)", "0x2406cedb": "setPackageOwner(bytes32,address)", "0xb7297cf3": "gameSettings()", "0xe94acf0e": "TinyRouter(address)", "0x4a2b0c38": "DividendProfit()", "0x0e3f732a": "TheGame()", "0xd62457f6": "callValue()", "0x4961b40c": "getReleaseValidator()", "0x540cafe0": "storeHeaderWithFee(bytes,int256,address)", "0x7ff729fc": "fillUpProject(uint256,uint256)", "0x253459e3": "feesSeperateFromBalanceApproximately()", "0x930a80b4": "testAuthorizedSetPackage()", "0xb3cb8885": "nextUnderdogPayout()", "0x62c7855b": "getConfigBytes(bytes32)", "0x4f28af6a": "handleBet(uint256)", "0x103f9251": "transferFrom(address,address)", "0x9b19251a": "whitelist(address)", "0x9928811b": "testBroken()", "0xb33a8a11": "setTokenReference(address)", "0x27f06fff": "requestFillUp(uint256)", "0x2f570a23": "test(bytes)", "0x96ef7aa0": "cash_transfered(string)", "0x3983d5c4": "calcBaseFee(uint256)", "0xec0f1025": "testBitsOrSuccess()", "0xd35f4a99": "mint(int256,address,uint256)", "0x09dfdc71": "currentPyramidBalanceApproximately()", "0xac4e73f9": "proposeReverse(string,address)", "0xac4bd53a": "currentLeader()", "0x5a2ee019": "m()", "0xeba36dbd": "setAddr(uint256,address)", "0x0358d965": "addPayout(uint256)", "0xd7206124": "setInvestorLock(bool)", "0xe916d0f0": "doBalance(address)", "0x67c2a360": "authorizeUser(address)", "0x828d671c": "dyn_sig()", "0xaf6fe8e2": "testGetToken()", "0x283a4576": "Tomeka()", "0x8ac0ca36": "buyViaJohan()", "0xcc872b66": "issue(uint256)", "0xd826f88f": "reset()", "0x2aa3177a": "self_store()", "0x53b7b2e9": "cEthereumlotteryNet(bytes)", "0xce88b145": "getAccount(uint256)", "0x1fa03a2b": "isApprovedFor(address,address)", "0xe42d5be0": "getPaymentOf(address)", "0xb722a9ef": "getPreviousShareholder(address)", "0xfadf87b1": "testGetBitsSuccess()", "0xd26c8a8a": "coinBalance()", "0x30ccebb5": "getStatus(address)", "0x47799da8": "last()", "0x4a5db3b5": "authorizeAddress(address)", "0x22686250": "index(int256,uint256)", "0x07ef99a0": "demintTokens(int256,address,uint8)", "0xea2d4cf8": "__DeployerFunctions(address,address,uint256)", "0x092a2e37": "multiAccessAddOwnerD(address,address)", "0x671fa0a0": "Inscription(string)", "0xa10edc55": "GeneralPurposeProfitSplitter()", "0xd9c67404": "getMerkleRoot(bytes)", "0xdc419fd8": "cancelOrder(bool,uint256)", "0xc9734ebd": "WatchLastPayout()", "0xc7d6faf1": "easyPropose(address,uint256)", "0xfe63300a": "registerExternalBill(uint256,address,address,uint256,uint256,uint256)", "0xd3437fe0": "assertFact(uint256,bytes)", "0x5fb3e119": "Auction()", "0x665beae7": "ExecutableBase(bytes)", "0xc8bb73ef": "testGetBitsFailIndexOOB()", "0xc1d4f708": "getMwLength()", "0x22b0f6ee": "getStatusOfPayout(uint256)", "0x21520c5f": "calculatePayout(uint8,bool,uint256)", "0x66e98c31": "createCoin(string,uint256,uint256,string,string,address)", "0x7b352962": "isFinished()", "0x48d9614d": "GetFee()", "0xfe0d94c1": "execute(uint256)", "0xe4547f98": "documentExists(bytes)", "0x4e10c3ee": "transferWithoutReward(address,uint256)", "0x58ae8bcf": "voteInMasterKey(address)", "0x1dcb304b": "fipsGenerate()", "0xb595181f": "ShapeshiftBot()", "0xe02426c1": "getSignatureHash(bytes4,uint256)", "0x67546967": "EthBtcEscrow()", "0x85b31d7b": "myInfo()", "0xaa677354": "register(address,address)", "0x1d2e2cc4": "ENS()", "0x1097e579": "Enter()", "0x13a396d8": "getRequiredDeposit(bytes)", "0x6df3edef": "getSavedBytes()", "0x09b30ed5": "afterExecute(address)", "0x718e6302": "play(string)", "0x8e46fbb2": "testBitsXorFailIndexOOB()", "0x0d87a7c0": "WLBDrawsDB()", "0xbbe42771": "closeDeed(uint256)", "0xedfbf7b6": "setVotingDeadline(uint256)", "0x299e6b07": "Wallet(address)", "0x5cbc85d0": "returnBounty(uint256)", "0xe5225381": "collect()", "0x94f60a63": "getKudosLeft(address)", "0xd6960697": "confirmPurchase()", "0x4a1f0bf6": "inheritToNextGeneration(address)", "0x244ded7a": "ChangeOwnership(address)", "0x39b35753": "authCancel(address)", "0x75cb2672": "configure(address)", "0x938ae4cc": "testThrowDisownNotTransferable()", "0x04fc11d5": "getActual()", "0xacab021c": "getTOS(address)", "0x812cddf2": "getSavedString()", "0x8ae475a9": "notorize(string)", "0xb1d05422": "SendEmail(string,string)", "0x0fffbb54": "changeRankingSize(uint256)", "0xb6ed9f15": "PFOffer(address,address,bytes,uint256,uint256,uint128)", "0xda333ca6": "payOut(uint256)", "0x652f1f16": "addSignature(string)", "0x983b2d56": "addMinter(address)", "0x5e1936d4": "testThrowSetNotTransferableNotOwner()", "0x4ac1ad78": "getWeekday(uint256)", "0x6ba0b4f2": "isKnownSelector(bytes4)", "0x7c4c27c8": "isThisPuritanicalVersion()", "0x7ae2b5c7": "min(uint256,uint256)", "0x63bd1d4a": "payout()", "0x3fd94686": "changeEligibleDonkeys(uint256)", "0x7fe0518a": "asyncSend(address,uint256)", "0x5a8dd79f": "getDesignatedCaller(address,uint256)", "0x2635f4de": "registerLibrary(bytes,address)", "0x1335ff36": "createEventAndMarketMaker(uint256,uint256,uint8,uint32,address,uint256,uint8,uint16,uint256)", "0x181be00d": "getValue(uint8)", "0x9c1500f0": "registerMany(address,uint256,int256,uint256,bytes,address,bytes)", "0x16f9ce49": "_slotCommitNew(address)", "0x8ca4eef6": "getBuild(bytes32)", "0x8ee21b8e": "get_default_keys()", "0xa66f7ad6": "signRelease(uint256)", "0x414053be": "best_adjustment_for(bool,uint128)", "0x83a51ad0": "oraclize_setConfig(bytes32)", "0x262c0b72": "getPayoutFreezePeriod()", "0x499af77c": "current_spin_number()", "0x4209fff1": "isUser(address)", "0x6e1b6bcc": "checkMyBet(address)", "0xb46300ec": "send()", "0x6b1cb549": "orderMatch(uint256,uint256,uint256,int256,uint256,uint256,address,uint8,bytes32,bytes32,int256)", "0x58d9fa04": "addUser(uint256,address)", "0x24c93343": "error(string)", "0xa95d017d": "getRevisionBlockNumber(bytes32,uint256)", "0x46af23f5": "InstantLottery(address,address,bool,address)", "0x29ef56b1": "getAskOrderBookStats()", "0xe97db66e": "setJackpot()", "0x4b59e880": "puzzle(address,bytes32,bytes32)", "0xc7f86c37": "withdrawFundsRP()", "0x57d4021b": "nextPayoutWhenPyramidBalanceTotalsApproximately()", "0xf1c760ae": "fixBalanceInternal(address)", "0x0908178f": "NoFeePonzi()", "0x22ebb3ac": "DieselPricePeg()", "0xb39a64cd": "getNumCalled()", "0x1c02708d": "killContract()", "0x65228934": "setOperationsCallGas(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x3397ca17": "numBalanceRecords(address)", "0xe436bdf3": "Draws(uint256)", "0xaf55bba0": "removeRegistryFromTagsIndex(address)", "0x11103599": "Token_Offer(address,address,uint16)", "0xb3a2a6c0": "setOfficialWebsite(string)", "0xb06eb03f": "DSEasyMultisig(uint256,uint256,uint256)", "0x775c300c": "deploy()", "0xb1c6517a": "LookAtNumberOfPlayers()", "0xc19d93fb": "state()", "0xac9873c7": "CanaryV7()", "0x750cae6a": "enableBetting_only_Dev()", "0xdf3c8620": "num_challenges()", "0xbb00fc55": "bookEarnings()", "0x0bd2ae1c": "ERW()", "0x580bdf3c": "disableBetting_only_Dev()", "0x5c3f9765": "endDateClose()", "0xc4128b6d": "upgradeCount()", "0x140b4465": "executeSpendingRequests()", "0xfaa1a8ff": "getOwnedBot(address,uint256)", "0x40e58ee5": "cancel(uint256)", "0xf4ea95b9": "validateReleaseVersion(uint32[3])", "0x2ba0b09f": "AddNewCategory(bytes4,uint8,uint8,address)", "0x55db4092": "setTOS(address,bool)", "0xf9cc0605": "getAvailable()", "0x3f2f46b4": "revealRock(string)", "0x2af7ceff": "testPrice(uint256)", "0xcaaf2dd7": "getInitialAnswerResult(uint256)", "0x6f36ce79": "insert_deal(address,address,uint64,uint128,uint32)", "0xa18c751e": "set(bytes,bytes)", "0x4d536fe3": "doit()", "0x3197cbb6": "endTime()", "0xb83069c5": "getStemPrice()", "0x3f15457f": "ens()", "0x81ebdeea": "testThrowCreateWithNonceRetracted()", "0x249b4d0b": "removeTrustedIssuer(address,bytes)", "0xe7b48f74": "get(int256,address)", "0x089d5c4a": "repr()", "0x3a9e7433": "scheduleCall(bytes4,uint256,uint256,uint8)", "0x504f1671": "getSize(address)", "0xf3a44fe1": "withdrawForWorkshop()", "0x7a837213": "setAllowedAccount(address)", "0x4551b1d7": "ProxyPayment(address,address)", "0x5dac1601": "SimpleStablecoin()", "0x87914c6f": "prolongateContract()", "0xe3ceb06d": "YesNo(bytes32,address,string,address,uint256)", "0x42a745cb": "testBitEqualSuccess()", "0xaa8dea8c": "fipsAddToLedger(bytes20,address,bytes)", "0x986dcd4d": "setCycleLimit(uint256)", "0x1e44c112": "find_strike(uint64,uint32,uint32)", "0x1ecfe64d": "_jSub(uint256,uint256,uint256,uint256)", "0x4ae85627": "grindUnicorns(uint256)", "0x200ebe34": "addTokensToGive(address)", "0x7b632c41": "TimestampScheduler(address,address)", "0x979b6f6f": "RoundInfo()", "0xb3ea3924": "PointlessCoin(int256,uint256,string,uint8,string,address)", "0x1d834a1b": "insert(uint256,uint256)", "0x931df75f": "validateProposedThroneName(bytes)", "0x6189be15": "columnround(uint256,uint256)", "0xdf5dd1a5": "addOracle(address)", "0x22d8cf5b": "CheckUserVote(uint8,uint8)", "0xdd62ed3e": "allowance(address,address)", "0xc0eb2325": "scheduleTransaction(address,bytes,uint256)", "0x038461ea": "getCertifiedStudentsCount()", "0x9ee035c9": "lookupCanonicalFormat(bytes)", "0x0ab58ead": "SingularDTVFund()", "0x550ed1f0": "getMaxBetAmount()", "0x6e63015c": "getCertifiersCount()", "0xbbd4f854": "buyShares(bytes32,uint8,uint256,uint256)", "0x306b031d": "getGenerationEndAt(uint256)", "0x1bd9c46e": "setImporter()", "0xc80c28a2": "getNumberOfParticipants()", "0xcb553ac9": "sweepWizardCommission(uint256)", "0x6389654e": "changeDailyWithdrawalLimit(uint256)", "0xc0a963c9": "notifyWinner(address,uint256)", "0x3c335b0e": "getRetractable(bytes20)", "0x017972af": "getNumbersFromHash(bytes32)", "0x07da68f5": "stop()", "0x1e8c72b4": "incrUserAvailBal(address,uint256,bool)", "0x0df71602": "setWinner(uint256)", "0x85e5bb3a": "Security_AddPasswordSha3HashToBankAccount(bytes32)", "0x2ade6c36": "getNodeAddress(bytes32)", "0xb33926cb": "owner_withdraw(uint256)", "0x57764094": "getRate(uint256)", "0x13827950": "getShareholderDB()", "0x26826bf8": "setImage(bytes)", "0x76d438b0": "sendReward(uint256,uint256)", "0x51d6e547": "getNonce(bytes)", "0x4f24186a": "newProposal(string)", "0x1a695230": "transfer(address)", "0xe820a32f": "vetoPayout(uint256,uint256)", "0xfb3a1fb2": "getReleaseDb()", "0x6bae05cf": "preRegister(address)", "0xae2df7b3": "setImporterBank()", "0x56d73ad1": "getCertifierDb()", "0x8396392d": "add(string,string,string,address)", "0xeb08b304": "changeMeatProvider(address)", "0xb60e72cc": "log(string,uint256)", "0x76999896": "KingOfTheEtherThrone()", "0xea851885": "buyStake(bool)", "0xa5982885": "assertFalse(bool)", "0xfbf1f78a": "unapprove(address)", "0xc2ba5b40": "getPackageData(string)", "0x83d51a38": "concatString(string)", "0x68af4971": "registerListening()", "0x14ba5c09": "getDay()", "0xe4849b32": "sell(uint256)", "0x44dd4b3b": "lookupGeneration(uint256)", "0x3fda1281": "get_keys()", "0x09241200": "wasSuccessful()", "0xbc9147a4": "Foundation()", "0xe33734fd": "changeProposalDeposit(uint256)", "0x2cce4abe": "_finishNoCallback()", "0x45e965cd": "strConcat(string,string,string,string)", "0xd4245e5b": "transferringETH(address)", "0x10082bff": "getActorBillXdetail(address,uint256,bool)", "0xc831391d": "getPoolOverlapSize()", "0x15e33901": "digest(bytes,uint256)", "0x5f0edfb8": "create(bytes,bytes32,bytes1)", "0xadd4c784": "getResult(bytes32)", "0x3233c686": "claimerDeposit()", "0x187a62d5": "voteEmergencyWithdrawal(bool)", "0x5404bbf7": "getEntropy()", "0x3f5b7675": "periodTwo()", "0x7ec0f30d": "ack(string)", "0xd30a512e": "betOnColumnOrDozen(bool,bool,bool)", "0x9fcbc738": "setIntermediate(address)", "0x539e2bfb": "secondChainedCallback(uint256)", "0xe724529c": "freezeAccount(address,bool)", "0x5aa97eeb": "getMarkets(bytes32[],address)", "0xc51be90f": "query_withGasLimit(uint256,string,string,uint256)", "0x531b97d7": "oneCentOfWei()", "0xeace4827": "player_make_bet(uint8)", "0x9c851ebc": "new_entry()", "0x98c9cdf4": "getMinimumCallGas()", "0xd8f012c6": "StatelessFactory(string,string,string)", "0xceeafd9d": "withdrawFundsAdvancedRP(address,uint256,uint256)", "0xd6d7d525": "get(bytes)", "0xbf55486b": "Tanya()", "0xd35ada32": "addParticipant(address,address)", "0xa8239d0b": "getPrice(string,address)", "0x12514bba": "transfer(uint256)", "0xb00140aa": "getHash(bytes)", "0x36f66528": "EtherDelta(address,uint256,uint256)", "0x279e0912": "getDownloadPrice()", "0x8173b813": "setNumCities(uint256,uint256)", "0xb98fdc36": "IconomiToken(uint256,string,uint8,string,uint256)", "0x3c7a3aff": "commit()", "0xcac77df7": "__transferFromToICAPWithReference(address,bytes32,uint256,string)", "0xfbeaebc6": "murder()", "0x2fa7cbfb": "getExecCost(uint256)", "0xe44d3084": "testFailure()", "0xede8acdb": "startAuction(bytes32)", "0xd5544f94": "getFundsAndAvailable(address)", "0x3824d8ee": "buy100DaoFor1Eth()", "0xbe3945e4": "getFee(address,address,uint256)", "0x1917ab5c": "activate(string)", "0x23509e69": "donkeysEligibleForFees()", "0xf460590b": "updateSigner(address,bool)", "0xc55c1cb6": "queryN_withGasLimit(uint256,string,bytes,uint256)", "0xd96d7ea2": "PRE_EXECUTION_GAS()", "0x1f7b6d32": "length()", "0xacf8bf2a": "channelCount()", "0x18968a03": "finalize(uint256,address,address)", "0x5674a3ed": "runLottery()", "0xe3280126": "addOrder(string,bool)", "0x32afa2f9": "claimEtherOwner(uint256)", "0x355474d2": "commitReading(address)", "0x00601d6c": "board(uint256,uint8,uint8)", "0x2667f407": "__proxy(address,bytes)", "0x79716e43": "confirmTransaction(bytes32)", "0x272cda88": "EternalDB()", "0x76ca0c77": "scheduleCall(address,bytes,uint256,bytes,uint256)", "0x338b5dea": "depositToken(address,uint256)", "0xfa93f883": "getMinute(uint256)", "0xafc4a982": "PathCost(uint16,uint32)", "0xf7d97577": "setPrice(uint256,uint256)", "0x842bc37b": "GetSmallCotractIndex(address)", "0x75f40f40": "underdogPayoutFund()", "0x23a1c271": "setPongval(int8)", "0x02571be3": "owner(bytes32)", "0xf79b22e0": "betOnATeam(uint256)", "0x10cf5d47": "awaitingPayout()", "0x1b00fe51": "testHypothesis()", "0xd449ce7c": "Administered()", "0x455259cb": "getGasPrice()", "0x975057e7": "store()", "0xdfdb5f17": "doBurn(address,uint256)", "0xaa497b9d": "scheduleCall(address,uint256,bytes,uint256,uint256,uint8)", "0xcabfb934": "replace(address)", "0x1f1f5e76": "addValueToContribution(uint256)", "0xa0eda9f2": "_transferFee(address,uint256,string)", "0xa8d95fb2": "claim(address,string)", "0x03ee8f08": "getCoeff(uint16)", "0x9872a20a": "registerUInt(address,uint256)", "0xb20d30a9": "setDailyLimit(uint256)", "0xe116b17e": "getKudosLeftForProject(address,address)", "0xf7c9f74a": "insert_contribution(address,uint256)", "0xc7a1865b": "play(bytes32)", "0x356594ab": "EtherTransfer()", "0xe22b0c46": "verify(uint256,uint256,uint8,bytes,bytes)", "0x2fea7b81": "getIdentity(address)", "0x6fa8de90": "changeMeatParameters(uint256,uint256)", "0x37c390e3": "allow_move(uint16)", "0xd22c391a": "validateProposedThroneRules(uint256,uint256,uint256,uint256,uint256)", "0xf6d339e4": "setAddress(bytes32,string,address)", "0xd7cc8362": "isLatestMajorTree(bytes32,bytes32)", "0xdd012a15": "setIt(uint256)", "0x254c91b3": "testBitNotSetSuccess()", "0x47e40553": "nextRound()", "0xa6b206bf": "doSomething(uint256)", "0xac996e7e": "resolvePledging()", "0x71e2d919": "lol()", "0x07d5b826": "buyAllOutcomes(bytes32,uint256)", "0x5f68804e": "SimpleLotto()", "0xa510f776": "setCompany()", "0x0d48e8d0": "doBalance()", "0xd21d7950": "changeGasLimitOfSafeSend(uint256)", "0x3358d2d3": "buildDSTokenFrontend()", "0xec93cfae": "FountainOfWealth()", "0x65b1fdf4": "scheduleIssuePOIs()", "0xdc3080f2": "spentAllowance(address,address)", "0xd1f0bb2d": "populateAllowedFreeExchanges()", "0xd591221f": "testTransfer()", "0xf24b5779": "removeTrustedIssuer(address,string)", "0xed4b1d0d": "scheduleTransaction(uint256)", "0xa83627de": "updatePeriod()", "0xf597a499": "UserDatabase(uint256)", "0x21f8a721": "getAddress(bytes32)", "0x5548c837": "Deposit(address,address,uint256)", "0x55b775ea": "setFeed(address)", "0x01b869f1": "release(uint32,uint32,uint32,bytes)", "0x609ff1bd": "winningProposal()", "0xdf98ef33": "getResource(bytes,uint256,bytes)", "0xd39eb301": "getStatus(uint8,uint8)", "0x2cc0b254": "init(address,bytes32)", "0x4228974c": "Videos()", "0xc431f885": "addToContribution()", "0x00e43ee9": "setMigrationStatus(uint256,address)", "0xd9f8a4e2": "calcCurrentTokenPrice()", "0xb9e6f1d9": "get_amount()", "0x6b3a87d2": "WatchWinningPot()", "0xef4592fb": "getResult(bytes)", "0x41b9dc2b": "has(bytes32,bytes32)", "0x3b9901cc": "getChannelsByRanks(address,uint256,uint256)", "0x83876bc9": "newProposalInWei(address,uint256,string,bytes)", "0x50a3bd39": "enterPool()", "0xc976bbbb": "_compare(int256,bytes2,int256)", "0x0e47c76f": "rotate(uint64,uint256)", "0x6f85c7e4": "WAITING_PERIOD()", "0x7075b1d8": "latestMonarchInternal()", "0x9209b3c0": "getCrtDetails(bytes)", "0x305075db": "NormalizeRanks()", "0xb6b55f25": "deposit(uint256)", "0xf09ea2a6": "offer(uint256,address,uint256,address)", "0xf1320af2": "exempt(address)", "0xc813c30e": "testThrowSomething()", "0x4faa2d54": "getTimeElapsed()", "0x22017c5f": "DSTokenBase(uint256)", "0x6637b882": "setDao(address)", "0xd0b52156": "getIpfsHash(address,address)", "0x13bd4e2c": "_prepareAndSendReward()", "0xdb7ca38a": "XaurmProxyContract()", "0x1bad1d2e": "monitorWallet(address)", "0x691f3431": "name(bytes32)", "0x3169ff3e": "LooneyLottery()", "0x446a7974": "Fokitol()", "0xdc3ab866": "checkEarnings(address)", "0xfad9bf9e": "storeBlockWithFeeAndRecipient(bytes,int256,int256,bytes,int256,int256)", "0xaf408d89": "setStatus(bytes)", "0xd02bf162": "spinTheWheel()", "0x9a36f932": "feeDivisor()", "0xca77ab8a": "getNextFile(bytes)", "0xc8e49707": "activateExportFee(address)", "0x502414e4": "marketMaker(string)", "0x78ec81a0": "sendEarnings(address)", "0x14167bf0": "oraclize_query(string,string[])", "0xba13a572": "lottery()", "0x299ed37a": "emergencyCall()", "0x6ec3af26": "addTrustedIssuer(address,bytes)", "0x3d69b403": "isOutcomeSet(bytes)", "0x9a19a953": "setInt8(int8)", "0x7817a60f": "acceptMember(address,string)", "0x1e223143": "getFirst()", "0x5437b39b": "hasUnprocessedDividends(address)", "0x8f0c724c": "setOperationsCallGas(uint256)", "0x3c925f16": "getAccountHolder()", "0x477801b1": "getLastRoundResults_by_index(uint256)", "0x3d21aa42": "sendApproval(address,uint256,address)", "0xd2b8035a": "draw(uint256,uint256)", "0x19c32e0b": "hmacsha256(bytes,bytes)", "0x28f90e4b": "Etheramid2()", "0xe87df70e": "fivetimes()", "0xc028df06": "offer()", "0xacb6c69b": "setTrustedClient(address)", "0xac7ffae3": "updt(uint256,string,uint256,uint256,string,string,address)", "0x0e5ffb3c": "hashVersion(uint32,uint32,uint32,string,string)", "0x4ac6b2be": "getCheckRecordCreator(bytes)", "0x93dafba2": "getSubpot(uint256)", "0x592685d5": "getWindowStart(address,address)", "0xc24a0f8b": "endDate()", "0x3f9b250a": "getDocument(uint256)", "0xd845a4b3": "request(uint256)", "0xa5bfa9a9": "claimToken(bytes32)", "0x8e3957d9": "RandomNumber()", "0x62fb09b2": "getRefDescr(uint256)", "0x9f35d3b2": "start(string,string,uint256,uint256,uint256,uint256)", "0xea1bf386": "getNextSellerBOTdata(uint256)", "0xe27fe50f": "startAuctions(bytes32[])", "0x031d973e": "closeMarket(bytes32)", "0x754dea40": "setBackendOwner(address)", "0xd83a8d11": "testProposing()", "0xf24a534e": "Oracle()", "0xd08275f1": "WolframAlpha()", "0x932db761": "profitsFromBitnationDebitCard()", "0xd96aee49": "MultipleConstructorTest()", "0x4594d06a": "delMinter(int256,address)", "0xb9f256cd": "newProposalInEther(address,uint256,string,bytes)", "0x1d2bca17": "MyToken(uint256,string,uint8,string)", "0x6edbd134": "hasHash()", "0x847f8a10": "Refund(uint32)", "0xb0604a26": "schedule()", "0x6676871d": "reserved_funds()", "0x05888fcd": "tradeBalances(address,uint256,address,uint256,address,uint256)", "0xe771066f": "marriageProof(bytes)", "0x7e32a592": "repairTheCastle()", "0xbab2f552": "currentCycle()", "0x45a3b0bf": "resolveFailPledge()", "0x070a888f": "updateRewardDuration(uint256)", "0x20d9822e": "setAnyoneCanCall(address,string,bool)", "0x224993c2": "setTimeBlock(uint256)", "0x01bd4051": "disown(string)", "0x4bc2a657": "setVoter(address)", "0x522103fa": "changeUnicorn(uint256,address)", "0xc988d70f": "getDailyWithdrawLimit()", "0x2f7f3ecf": "findNextHour(uint256,bytes)", "0x1b769e74": "testThrowsRestartNotUpdatable()", "0x4d561721": "etherandomSetNetwork()", "0x92a781d8": "changeBaseValue(uint256)", "0xf0a78538": "scheduleTransaction(uint256,bytes)", "0x64ef212e": "proxyTransferWithReference(address,uint256,bytes32,string)", "0x97c3ccd8": "ban(address)", "0xdeb6930c": "PriceTicker()", "0x6cc5fdaa": "setBytes32(bytes,bytes)", "0x92c8eb96": "DSFalseFallbackTest()", "0x6534b4e2": "IsPayoutReady__InfoFunction(bytes32)", "0x15e812ad": "getBaseFee()", "0xf5b53e17": "getInt256()", "0x081bf263": "isOOB(uint8,uint8)", "0xd2fb8787": "recordExists(bytes)", "0x86c57fcc": "b32ToBytes(bytes)", "0xe3a199d6": "testThrowCreateNewRevisionNotUpdatable()", "0x57cfeeee": "transfer(address,uint256,bytes32)", "0xb72e717d": "fromAddress(address)", "0x61b20d8c": "retrieveFunds()", "0xf4b103d4": "SimpleStorage(uint256)", "0xe2056c46": "ExtraBalToken()", "0xc3ee6311": "lockAndCall(string)", "0x136af582": "next(bytes,bytes,bytes,bytes,bytes,bytes,bytes)", "0xc5d5997c": "changeSubUser(address,address)", "0xf8018a79": "prepend(address,address)", "0xf6b4dfb4": "contractAddress()", "0x9450b1c8": "addCharityFundation(string,string,string)", "0xd1a8d447": "get_all_bet_values()", "0x66d8c463": "reveal(bytes32,string)", "0xbb963c8a": "transferLibOwnership(bytes,address)", "0xd56b2889": "finish()", "0x9fb25d9e": "LeaderMessage()", "0x6ad50ed4": "investmentEntryInfos()", "0xa4d575ce": "_forward(address,bytes)", "0xa3ec5616": "next(bytes,bytes,bytes,bytes,bytes,bytes,bytes,uint256)", "0x0db73c72": "noevent()", "0xf449619e": "collectPrize(uint256)", "0x1afccfa5": "Proposal(address,address,address,bytes,bool)", "0x4788cabf": "getContractId()", "0x4112987c": "strConcat(string,string,string)", "0x928a00d2": "deleteCoin(uint256)", "0xd9ec0508": "testThrowTransferNotEnabled()", "0x9aaf442c": "applyCensorship(uint256)", "0x49407a44": "claimEther(uint256)", "0x4fcf8210": "eraseRecord(bytes32)", "0x5ed84aa6": "getNymCenterAPIURL()", "0xa3c2c462": "totalReceived()", "0x5938748e": "changeVotingRules(address,address,uint256,uint256,uint256)", "0x6fc9d5e4": "changeCompareTo(uint256)", "0xe2ee9941": "tap(bytes20)", "0x0ff4f160": "oraclize_query(uint256,string,string[1])", "0x43ec3f38": "toSliceB32(bytes32)", "0x3288eb0b": "ChineseCookies()", "0x373c98a2": "authCall(address,bytes32)", "0x6ea056a9": "sweep(address,uint256)", "0x8dc45377": "getDuel1(uint256)", "0xd4625a3a": "equals()", "0x616fca9b": "adopt(address)", "0x32cea83e": "birth(bytes)", "0x002a5cc9": "getTicketHolders(uint256)", "0x31119b4d": "changeDeveloper(address)", "0x69569a51": "setFrontend(address)", "0xb7e24979": "addThing(bytes)", "0x164e68de": "withdrawFees(address)", "0x42bf4431": "orderMatchTest(uint256,uint256,uint256,int256,uint256,uint256,address,address,int256)", "0x5dd672ec": "latestBid()", "0x45fe6e2a": "Scheduler()", "0x55cc4e57": "setIssuer(address)", "0x1df5e755": "Etherandom()", "0x8f70009d": "id_for_address(address,address)", "0x39b50688": "cancelSellOrder()", "0x40953102": "scheduleCall(address,uint256,bytes,uint256,uint256,uint8,uint256)", "0x677913e9": "setAmount(int32)", "0x66671c71": "BaseScheduler(address,address)", "0xfe71aec5": "LittleCactus()", "0x879d46fd": "DAOTrust(address,address,bytes,uint256,uint256,uint128)", "0x3b143184": "Congress(uint256,uint256,int256,address)", "0x7370a38d": "getNumPackages()", "0xfee35ff8": "newInvest(uint256,address,uint256)", "0x57dc9760": "DaoChallenge()", "0x8ac78c80": "Docsign()", "0x76d66f5d": "_Transfer(address,address,bytes32)", "0x9dc2c8f5": "fireEventLog4Anonym()", "0x43114842": "acceptChallenge(uint256,uint256,uint256)", "0x2043285d": "getMarketMakers()", "0xfb32f4f5": "ARK_FLAGGER_1_00()", "0x69431ab6": "TokenCreation(uint256,uint256,address,string,string,uint8)", "0x045c6ce0": "voteForProposal(uint256)", "0xa140e79c": "setMinimumDebatePeriod(uint256)", "0x331a72d1": "getRetractable(bytes32)", "0x87cc1e1c": "setExporterBank()", "0x7183616c": "notarize(string)", "0xce79add1": "givableBalanceOf(address)", "0x6f0cfab6": "DNSResolver()", "0x1c2f38ff": "paid(uint64)", "0xb7de47d3": "getIndex(uint256,uint256)", "0x04a2b2c2": "testOwnerCanBreach()", "0x65a4dfb3": "oraclize_query(uint256,string,string,string,uint256)", "0xf2ddc772": "confirm(bytes)", "0xa00ce377": "getIsContractValid()", "0xfe9fbb80": "isAuthorized(address)", "0xbbd39ac0": "coinBalanceOf(address)", "0xa4fde8bc": "player_declare_taking_too_long()", "0xd052fbf6": "getHistory(string,uint256)", "0xd205ad7d": "proposeDissolve(bytes)", "0x5a3b7e42": "standard()", "0xf27197ab": "getIsAvailable()", "0x00a94b6e": "oraclize_query(uint256,string,string[5],uint256)", "0x971c803f": "getMinimumStackCheck()", "0x5168afa4": "getPackageHash(bytes,uint8,uint8,uint8)", "0xaef99eef": "Game()", "0x7f924c4e": "testDeposit()", "0xb1adc241": "BalanceDB()", "0x6a704d7b": "AddedToGeneration(address,uint256)", "0xb9f37c86": "Registrar()", "0xc631b292": "closeVoting()", "0x19350aea": "nameFor(address)", "0x85dee34c": "query2_withGasLimit(uint256,string,string,string,uint256)", "0xbf187478": "shift_left(uint64,uint256)", "0x5b6b431d": "Withdraw(uint256)", "0xe5dd90a5": "HumanStandardToken(uint256,string,uint8,string)", "0xbf8c50ff": "scheduleTransaction()", "0x013d64bd": "setCanCall(address,address,string,bool)", "0xb870ecbb": "testNormalWhitelistAdd()", "0xbcd3d8ca": "Collector(address,address,uint256)", "0x4401ff5c": "sellShares(bytes,uint8,uint256,uint256)", "0x3e0dfbdf": "getInvestorByAddress(address)", "0xcb3e64fd": "unhalt()", "0xafc24e3d": "getChallengeAnswer(uint256)", "0xa36c8ec2": "UpdateContractorAddress(address)", "0xd5a4a3c6": "findRecentBet(address)", "0xb028ee13": "s2b(string)", "0x692ad3a9": "round(uint256,uint256,uint256,uint256)", "0x0a7493b4": "Etheropt(uint256,string,uint256,uint256,bytes,address,int256[])", "0x60689557": "Rock()", "0x717fedf0": "getFirstActiveDuel1()", "0xe837ab59": "getParticipantByAddress(address)", "0x32d5fe98": "revealCampaign(uint256,uint256)", "0x0178b8bf": "resolver(bytes32)", "0x44faa139": "Withdraw(uint32)", "0x418cf199": "setEstimateCost(uint256,uint256)", "0xae45850b": "schedulerAddress()", "0xd4871517": "BTCLotto(address,uint256)", "0xfbffb355": "testBitsEqualFailIndexOOB()", "0x2be6d43c": "ARKTagger_1_00()", "0x1df47aad": "ReplayProtection()", "0xb36a0b15": "getSignDetails(uint256,uint8)", "0x9c4baf27": "Skywalker(address,address)", "0xfb34fc6f": "WatchNextBlockReward()", "0xceba30b5": "scheduleTransaction(address,bytes,uint256[4],uint256)", "0xfc94dd18": "verifyHumanStandardToken(address)", "0x1ff13086": "size(int256)", "0xce92dced": "newBid(bytes32)", "0x231944e2": "moveUnits(uint256,uint256,uint256[])", "0x784813e0": "lookupBet(uint256,uint256)", "0xe0e3ba5a": "getLosesShare(address)", "0x5c1b3ca1": "getConfigUint(int256,bytes32)", "0x91060168": "fetchString(address,bytes4,bytes32)", "0x354b2735": "testDeploy()", "0xb88eef53": "registryCreated()", "0x8e3d4e5e": "Fibonacci(bytes)", "0x64bd87d6": "scheduleCall(address,bytes,bytes,uint256,uint256)", "0x5c3d005d": "demote(address)", "0xb6509c12": "Ethereum_twelve_bagger()", "0xe87508be": "investorDeposit()", "0xcb96012e": "hashTo256(bytes32)", "0x314e0fb6": "scheduleTransaction(address,bytes,uint256[3],uint256)", "0x5b6a54bc": "adjustTransactionFee(uint256)", "0x5f515226": "checkBalance(address)", "0x76cd7cbc": "sign(bytes)", "0xce220ecf": "testAddBalanceFailsAboveOverflow()", "0x922fc84b": "taskProcessedNoCosting(uint256)", "0xf8b2cb4f": "getBalance(address)", "0x7a29332d": "buyAllOutcomes(uint256,uint256)", "0x04106c8b": "startGeneration()", "0x8eaa1e29": "getContentByData(address,uint256,string,string)", "0x5a7a8850": "rollWithSeed(bytes32)", "0x2facc4e8": "depositGovernance(uint256,address)", "0xf2561a43": "voteSuicide(address)", "0xee1b4828": "closeBooks()", "0x18f303a1": "SetInternalValues(uint8,uint256)", "0xce60f78d": "createMarriage(bytes,bytes,uint256,bytes,bytes)", "0x18b749c4": "payEther(uint256)", "0xbbc6eb1f": "getDefaultDonation()", "0x0b1ca49a": "removeMember(address)", "0xccf4f413": "setSubRegistrar(string,address)", "0x4616caa9": "pushCoin(uint256,address,string)", "0xd69450d5": "setUUID4Bytes(bytes)", "0xc98165b6": "createTarget()", "0x8c79a24d": "refName(uint256)", "0x56d88e27": "len()", "0xbfc3d84b": "CT()", "0xc7f2e6af": "Contribute(bytes20)", "0xd7e11e9d": "AddTicket(bytes)", "0x2d06177a": "addManager(address)", "0xd588acc4": "claimMiningReward()", "0x2ffb9e64": "updateGasForXaurData(uint256,uint256)", "0xf896503a": "getConfigAddress(bytes32)", "0x9af8c4ba": "respond(uint256,address,bytes)", "0x82a5285d": "getMinBetAmount()", "0x3ead67b5": "changeContractOwner(address)", "0xb29d7914": "getRefResults(uint256)", "0x135128c2": "CounterPartyDeposit()", "0x795b9a6f": "scheduleCall(address,bytes4,uint256,bytes)", "0x83197ef0": "destroy()", "0x433836dc": "scheduleTransaction(address,bytes,uint8,uint256[3],uint256)", "0xf009347d": "KudosProxy(address)", "0xf2fde38b": "transferOwnership(address)", "0x62c99e84": "_Approval(address,address,bytes32)", "0x1b83b823": "notifyPlayer(uint256)", "0xe56c8552": "spinTheWheel(address)", "0x93eec1fb": "setName(uint8,uint8,string)", "0x11af3c68": "divest(address)", "0x8279c7db": "setReceiverAddress(address)", "0x44691f7e": "hasStarted()", "0x349501b7": "checkDepth(uint256)", "0xe8beef5b": "fireEventLog3Anonym()", "0x0870607b": "addSubUser(address)", "0x063925c8": "scheduleCall(bytes,uint256,uint256)", "0xa23744f0": "tryCreateCheckRecord(bytes)", "0x35d79fad": "CertificationDb(address,uint256,address)", "0x44691f2b": "Dispute()", "0xd7ccc2c3": "getLastPayment()", "0x152583de": "getAttributes()", "0x1a9360dd": "checkDate()", "0x420ef2b3": "TargetHash()", "0xf0caea2b": "SmartRoulette()", "0x5d268629": "Refund()", "0x23385089": "emitApprove(address,address,uint256)", "0x7648c929": "returnRemainingEther()", "0x5d5bc4cb": "BetOnRed()", "0xde8fa431": "getSize()", "0xda6b31b9": "testErrorTransferToNullAuthority()", "0x444dd6f3": "Elcoin()", "0xe1fa8e84": "register(bytes32)", "0x3e0a322d": "setStartTime(uint256)", "0x21bacf28": "getDefaultFee()", "0xb1662d58": "setModule(address,bool)", "0x5b0fc9c3": "setOwner(bytes32,address)", "0x40193d17": "getPongvalConstant()", "0xfb95adeb": "testFailBlockhashInsuffiecientFee()", "0x8ecc0950": "returnToOwner()", "0x34b7ac9b": "END_MINTING()", "0xeaa37394": "create(bytes,bytes32,bool,bool,bool,bool,bool)", "0xd7bc23af": "newParameters(int256,uint256,uint256,uint256)", "0x97709cde": "ARK_VOTER_1_00(uint256,uint256,uint256,uint256,uint256,uint256)", "0xcf4a1612": "scheduleTransaction(uint256,address,bytes,uint256)", "0x40695625": "testRetractLatestRevision()", "0x90c3f38f": "setDescription(string)", "0x5fdf05d7": "two()", "0xaaac50bd": "transferDisable(bytes32)", "0x206a44f3": "getNum(bytes,uint256)", "0x7ac26aeb": "getTag(string,uint256)", "0xdc3f65d3": "createdByMe()", "0xe99543aa": "Trash(uint256)", "0x1bcad37a": "getTotalCost()", "0xa7f43779": "remove()", "0x3416f9d4": "subtractSafely(uint256,uint256)", "0x3bcf7d22": "newBribedCitizen(address)", "0x918f1bb5": "ProjectKudos()", "0xcf09e6e1": "SetBigContract(address)", "0x23add736": "claim(uint256,uint256,uint8,bytes,bytes)", "0x8ac6a869": "isObsolete()", "0x2324c67c": "getAllSignatureHashes(bytes4)", "0x981a60f5": "extractNameFromData(bytes)", "0xc018d0e6": "getFeeAmount(int256,int256)", "0x50e06b57": "Etherization()", "0x49e65440": "setSymbol(bytes32)", "0xfaee13b9": "set(int8)", "0x01ffc9a7": "supportsInterface(bytes4)", "0xc0b92612": "changePig(address)", "0x3f9f5b68": "setPreviousID(uint256,int256)", "0x2e9c5e77": "doStackExtension(uint256)", "0xc83be888": "single_move(uint256,uint8,uint8)", "0xef19c332": "_checkSigned(bytes32,uint256,uint8,bytes32,bytes32)", "0x2f30c6f6": "set(uint256,address)", "0x62986e27": "Canary(address,uint16)", "0x44dfdce0": "getNameOwner(bytes)", "0x4b7fcee7": "ownerPausePayouts(bool)", "0x84dac46e": "Fucksign()", "0x0878bc51": "getAttachesto(uint8)", "0x42d16748": "getMinDailyWithdrawalLimit()", "0x4ecd73e2": "DistributeDividends(uint256)", "0x03750d94": "serverSeed(address,bytes32)", "0xea0a5237": "announce(string)", "0x611daa7e": "EmergencyBalanceReset(uint256)", "0x2ae87a70": "getNumContents(address,uint256)", "0x5ec01e4d": "random()", "0xfd735602": "executeN()", "0xfebefd61": "startAuctionsAndBid(bytes32[],bytes32)", "0x8b64d70e": "owner_set_time_limit(uint256)", "0x2839e928": "ackermann(uint256,uint256)", "0xaf29e720": "remainingGasFund(uint256)", "0x9eee85fe": "bookEarnings(address,uint256)", "0x3733ffca": "convertTo(uint256,string,string)", "0xdbde1988": "transferFromWithoutReward(address,address,uint256)", "0xd3edcb5b": "getCreditorAddresses()", "0x77fcb91d": "forward(address,bool)", "0xf062e26b": "check_darkdao()", "0xdef2489b": "convert(address)", "0xccbda1af": "getChannelByName(string)", "0x267c8507": "authorizeManager(address)", "0xf1c30ec0": "reclaim(bytes)", "0xe6470fbe": "updateDefaultPayment()", "0x8fe58eb9": "Triger()", "0xd6b44859": "scheduleUndoIt(uint256)", "0x0eb8ed07": "transferEnable(bytes32)", "0xfc1f2a70": "Add(uint256,string,string)", "0x058026d0": "checkTransferToICAPWithReference(bytes32,uint256,string)", "0xaf27c7b3": "Security_HasPasswordSha3HashBeenAddedToBankAccount()", "0x5b151fd2": "fifty_fifty()", "0x531d1974": "testThrowRetractLatestRevisionEnforceRevisions()", "0x60213b88": "getInitialWithdrawal()", "0x29e94503": "VersionedBlob()", "0x9c5d7030": "reimburseGas(uint256,address,uint256,uint256)", "0xcd9a3c98": "any(bool[7])", "0xb484e532": "getMyMsg()", "0xd1f59db9": "isLatestMinorTree(bytes32,bytes32)", "0xe0c6190d": "checkTime()", "0xaf5610dd": "isThisPreforkVersion()", "0xfa968eea": "minBetAmount()", "0x003538c5": "TestRegistrar(address,bytes32)", "0x71c59097": "MainnetSurvey(uint256,string,bytes32[])", "0xc82aac47": "searchByTag(bytes32)", "0xeca5c793": "testErrorUnauthorizedNameRegister()", "0xdc75f2db": "multiowned(address[],uint256)", "0x4c9ed763": "requestTokensBack()", "0xbd9a5673": "oraclize_query(string,string[5])", "0xb11e3b82": "createEvent(bytes32,bool,int256,int256,uint8,address,address,bytes32[])", "0x86e4e178": "CheckTickets(address,uint256,uint256)", "0x53c84526": "setSmartAffiliateContract(address)", "0x89029d8c": "get_all(uint256,uint256)", "0xc25e6908": "ultimateOutcomes(bytes32)", "0x6b1e564a": "challengeWinningOutcome(bytes32,uint16)", "0x1c895915": "getNumberOfPayments(uint256)", "0x244fcd03": "removeRelease(bytes32,string)", "0x2ca6d2c0": "getAccountSize(address)", "0xfe67a54b": "endAuction()", "0x756fb8c9": "getOptionChain()", "0x88102583": "safeCastSigned(uint256)", "0x60063887": "transferDebt(address,address,address,uint256)", "0x16216f39": "return13()", "0x8089d001": "getHashOfBlock(uint256)", "0x27bc39c0": "submitCanonicalCandidate(bytes,bytes,bytes,bytes)", "0xb29ae23f": "getDateOfSignature()", "0xdd729530": "add_shield(uint16)", "0x38557648": "executeSellOrder(address)", "0xe2a71f12": "accountDelete()", "0xce869a64": "fails()", "0x69bdfd3a": "toContractDie(bytes,bytes,uint256)", "0x99aeade3": "iterateTable(uint256,uint256)", "0x46bdca9a": "equal(string,string)", "0x5e07f240": "shiftBitsLeft(bytes,uint256)", "0x76285b5b": "_is360thDay()", "0x594151e0": "Dice()", "0x4dc43eaf": "setTreasury(uint256,uint256)", "0xec727000": "getApprovalDB()", "0xb144adfb": "balance_of(address)", "0x63052d82": "getOwnersIndex(address)", "0x833b4596": "testApproveSetsAllowance()", "0xc67d376d": "getClosedCandidates()", "0xcf03f5f4": "activateMasterKey(address)", "0x7ca55e00": "etherandomVerify(bytes32,bytes32,bytes32,uint256,uint256)", "0xd92ebe46": "createDAO(address,uint256,uint256,uint256,string,string,uint8)", "0x3773930e": "ConfigureFunction(address,uint256,uint16,uint16,uint16)", "0xfee6d28c": "addSnapshot(string)", "0xe71264fa": "addNewTokens(uint256)", "0xc4d9102f": "setNextID(uint256,int256)", "0xa6f0e577": "isLeapYear(uint16)", "0xc7102df7": "__stopBlock()", "0xfa93019c": "getBlocks(uint8,uint8)", "0xe9794dc1": "CreateHash(uint8,string)", "0x5460ef10": "sendWithExtraGas(address,uint256,uint256)", "0x2f62a6ff": "fipsRegister(uint256,address,bytes)", "0xd630bd53": "pledgeApprove(uint256)", "0x3448c7d6": "createHistory(bytes,address,address)", "0xdda3342b": "ReplicatorFactory()", "0x0cd865ec": "recover(address)", "0xb9f28076": "historyIdx(address)", "0x4dda1764": "CafeMaker()", "0x883ba26b": "getIsSettled()", "0x3f5e268f": "convictInitial(uint256,uint256)", "0x4a3b0eec": "authorizeOpen(uint256,bool,string)", "0xee6d2641": "sendWithExtraGasExt(address,uint256,uint256)", "0xde14bbf7": "randomGen(uint256,uint256)", "0x7f3bd56e": "disburse(address,uint256)", "0x20d8741f": "Feed()", "0x60c6b3a5": "claim(bytes,address,uint256,uint8,bytes,bytes)", "0xda4b5e29": "contains()", "0xb3c25835": "addUser(address,string,string,uint256)", "0xee2af3fb": "set_factory(address)", "0xb821f815": "pay_winner(uint256)", "0x138cc941": "testErrorTransferToRejectAuthority()", "0xc0b6f0c2": "NextRoundAndEvents()", "0xc7e67360": "GAS_BUFFER()", "0x058d7433": "setAlliesContract(address)", "0xd810f298": "computeSettlementAmount()", "0xa24d23eb": "ProcessGame(uint256,uint256)", "0x7ac91cc2": "testFailOwnedAuth()", "0x79c3ddc1": "isPackageOwner(string,address,address)", "0x478ae93c": "playToWin(uint256)", "0x6632a507": "testSetupPrecondition()", "0xb6013cef": "finalize(uint256,uint256)", "0x37b7bf11": "Tile(int256,int256)", "0xecfc7ecc": "placeBid()", "0x70b1d9d4": "requestCanonicalFormat(bytes)", "0x315fdea3": "TreasureChest()", "0xc5575ef0": "checkTransferFrom(address,address,uint256)", "0x65c72840": "getDay(uint256)", "0xd6eafd08": "scheduleCall(address,bytes,bytes,uint8,uint256[4])", "0x350fbe2e": "calcNextDrawTime()", "0x8af784dc": "expectEventsExact(address)", "0x2db89533": "Auth(uint8,address)", "0x9f203255": "setAuditor(address)", "0x2526d960": "clawback()", "0x3fbd40fd": "ProcessDraw()", "0xface030b": "SpinTheWheel(address)", "0x648621ec": "xnotify(string)", "0x22dc36e2": "processed(uint64)", "0x6f52167d": "payDuel(address,string,address,string)", "0x8f70bfa0": "processDeposit()", "0x25ea269e": "Scissors()", "0x93feb13b": "ForceSendHelper(address)", "0xb688a363": "join()", "0x89859b50": "updateLatestTree(bytes32)", "0xf83d08ba": "lock()", "0x7d287697": "testTryGetUnset()", "0x98d5fdca": "getPrice()", "0xfe72e717": "toDie(bytes)", "0xb3c06f50": "transferFrom(address,address,bytes32)", "0x1465aa97": "testingContract()", "0x069d6d1c": "closeOrder(uint256)", "0xa79f26dc": "force()", "0xf2371fb3": "grantGiveableKudos(address,uint256)", "0xaa7dcd84": "testUpdateAuthorityEvent()", "0x0d8b5fa2": "testControllerValidTransferFrom()", "0x0e0f55d0": "RewardOrder(uint256,uint256)", "0x9ea1b79d": "getContentChannel(uint256)", "0x4a67fa7d": "setLotteryFee(uint256)", "0xdb006a75": "redeem(uint256)", "0x8f4ed333": "step2()", "0x1a10cfc3": "delete_entry(uint256,uint256,uint256)", "0xd422e4e0": "takeFee(address,uint256,string)", "0x61a00f6d": "Ballot(bytes32[])", "0x9c30936f": "removeCertificationDocumentFromSelf(bytes32)", "0xa5f4af33": "playerWithdrawPendingTransactions()", "0x07ad9ecb": "safeSend(address,uint256)", "0x8f99ea43": "setDividendDB(address)", "0x1df473bc": "newContract(bytes)", "0xea5ea470": "payFunding(uint256)", "0x743e0c9b": "receiveTokens(uint256)", "0x21835af6": "__dig(uint256)", "0x47448e8a": "set(bytes32,string,bytes32)", "0x9b1ad792": "destroyToken(address,uint256)", "0xf765088f": "UpdateClientAddress(address)", "0xddbbc35c": "searchByName(string)", "0x5ed7ca5b": "halt()", "0x97950740": "roomForBirth()", "0xfc01abbe": "stringToBytes32(string,string)", "0xea3d508a": "selector()", "0x8c88752a": "ContributorList(uint256)", "0x5837e083": "move_history(uint256)", "0xf7c3ee7a": "immortality()", "0x1b9f9647": "accessMyWallet(address)", "0xc8691b2a": "getHistory(uint256)", "0x91e8d3dc": "testBitOrFailIndexOOB()", "0x5c89c10d": "setBannedCycles(uint256[])", "0x4500054f": "isCancellable()", "0x334ef224": "testThrowsUpdateLatestRevisionNotOwner()", "0x763a738c": "allNames()", "0x45590ec8": "addTag(uint256,string)", "0xe7740cf9": "revealPaper(string)", "0xd9d2d058": "Splitter()", "0xb412d4d6": "CafeDelivered()", "0x8365172c": "num_levels()", "0x41c0e1b5": "kill()", "0x3106fea0": "voteOnProposal(uint256,bool,uint256)", "0x82ab890a": "update(uint256)", "0x4636a159": "newPhoneToAddr(address,uint256)", "0x2f29d8c5": "elapsed()", "0x1bf20668": "testAdminTransfer()", "0xf709dd51": "getTrademark()", "0x8b859409": "setRelease(bytes32,bytes32,string)", "0x03959bb7": "setDataContract(address)", "0x4247f52d": "DoRoll()", "0x31ab4066": "testAuthorityTryAuth()", "0xac4b2bae": "newParameters(int256,uint256,int256,uint256)", "0x57eaeddf": "_isContract()", "0x4a3a87e2": "CreateProxyWithControllerAndRecoveryKey(address,address,uint256,uint256)", "0xd116c8c4": "releasePayment()", "0x6615dd83": "setSeedSourceB(address)", "0xb8aca90b": "CurrentGame()", "0xc124e2ea": "checkBetDozen(uint8)", "0x4b0bbf84": "addEntropy()", "0x452fbc41": "USN(address,address,bytes,uint256,uint256,uint128)", "0xcdb6753b": "setNav(uint32)", "0xbb5d40eb": "isValid()", "0xd6f42038": "PhoneToAddress()", "0x6bc3e0f0": "verifySecondHalf(uint256[4],uint256[4],uint256[4])", "0x33893071": "checkMyWithdraw(address)", "0xfb46d4c5": "tweet(string)", "0x248582b0": "receivePaymentForGoodsSoldEarly()", "0x766a3f2e": "Security_ConnectBankAccountToNewOwnerAddress(uint32,string)", "0x1c8d5d38": "allowance(address,address,bytes32)", "0x6b256f57": "DAOSecurity(address,address,bytes,uint256,uint256,uint128)", "0xe8d1e961": "lockAccount(uint256)", "0x152fb125": "SimpleMixer()", "0xf72457af": "CertifierDb()", "0xe8a5282d": "setConfig(bytes32)", "0xbeb92f55": "setCaller(address)", "0x9a571d9f": "isAlphaLower(bytes1)", "0x46a2679a": "getSubpotsCount(uint256)", "0xd62d3115": "testCreate()", "0xb6ed0632": "cancelOrder(uint256,uint256)", "0xc95e81cb": "MyBet(uint8,address)", "0x1d5a9f3f": "object_types(uint256)", "0xa49d53a1": "SmartRevshare()", "0x5b65b9ab": "setFee(uint256,uint256,uint256)", "0x116c6eab": "getProfitShare(address)", "0x8e46afa9": "getDefaultGracePeriod()", "0xdabc706e": "getProposalCost()", "0x3fbb539d": "scheduleCall(address,bytes,uint256,bytes)", "0x86269a88": "checkBetNumber(uint8)", "0xb6ac24df": "updatePatchTree(bytes32)", "0x4637d827": "trust(address)", "0x1c1b8772": "update(address)", "0x5a9f2def": "scheduleCall(bytes4,bytes,uint256,uint256)", "0x81a60c0d": "getResults(uint256)", "0xd1b4ff7e": "multiAccessRevokeD(bytes32,address)", "0x92b7d5b9": "getCurrentGaslimit()", "0x77ceded8": "mintGrey(int256,address,uint256)", "0x2a095fbe": "unlinkEID(bytes,bytes,address)", "0xa6e16ba2": "testThrowsRetractLatestRevisionNotOwner()", "0x4579268a": "getOffer(uint256)", "0xcabb3a3a": "isAlphaNumeric(string)", "0xfadc51cf": "isAlpha(bytes1)", "0xf2022905": "toldYouItWouldWork()", "0x686e8aaa": "GetMoney()", "0x07718a3b": "BankOwner_WithdrawDonations()", "0xc58343ef": "getRequest(uint256)", "0x7b1a547c": "registerAs(address,string,uint256,string,address)", "0x213b9eb8": "setAddr(string,address)", "0x75090ebf": "changeDomain(uint256,uint256,uint256,address)", "0xdbbdf083": "register(uint256,address)", "0xfa4e5e5a": "notify(uint8,string,string)", "0x86a5ff97": "changeStatus(string)", "0xb8f71f26": "scheduleTransaction(uint256,address)", "0xa2ec191a": "addDSource(string,uint256)", "0x18b31f94": "registerLengthFunction(string,string,address)", "0x7b395487": "voteForUltimateOutcome(bytes32,uint16)", "0x39246d75": "VersionModel()", "0xd500dd6a": "challengeTimeout(uint256,bool,address)", "0xd1da09ee": "extractImportFeeChargeLength()", "0xc74e907b": "commit(address,uint256,uint256)", "0x4b09ebb2": "e_exp(uint256)", "0xec3af4a9": "getProjectKudos(address)", "0x714064f3": "BreakableBond(address,address,uint256)", "0xc4bc5da5": "resumeContract()", "0xf7888aec": "balanceOf(address,address)", "0x2f597e71": "testLongInput()", "0x7212b67e": "add_potion(uint16)", "0x9a15f4f3": "getBlockHeader(int256,int256)", "0x6eacd48a": "ownerPauseGame(bool)", "0xf739ed4c": "id_for_user_version(uint256,uint256)", "0xfaf0952b": "testThrowRestartNotOwner()", "0x88a1e895": "test2Fails()", "0x237e9492": "executeProposal(uint256,bytes)", "0x7cb97b2b": "set_owner(address)", "0x2bb685bc": "kill2()", "0xdc52696f": "tokenSupplyChanged()", "0x83d852d9": "shutdownTransactions()", "0x525b25b1": "getDeploymentReward()", "0xeac116c4": "createKingdom(string,address,address,address,address)", "0x014e5fde": "ARKController_1_00()", "0xc6ae3b57": "dEthereumlotteryNet(address,address)", "0xcddbe729": "game(uint256)", "0x8823a9c0": "changeFeeTake(uint256)", "0x021991e7": "getBetsLocked()", "0x3015394c": "cancelRequest(uint256)", "0x9d118770": "destroy(uint256)", "0xe854dfb4": "Order(address,uint256,uint256)", "0x8435be4b": "getLastFarm(uint8,uint8)", "0x27fbcac5": "getChannelFeed(address,uint256,uint256)", "0xc1be4031": "XaurumProxyERC20()", "0x8e25071a": "setProxyCurrator(address)", "0x4f139314": "compensateLatestMonarch(uint256)", "0x85c78fac": "retryOraclizeRequest(uint256)", "0x478e25bf": "resetAction(bytes32)", "0xc74c251f": "addSafely(uint256,uint256)", "0x058aace1": "divest()", "0x6d1da953": "createWithNonce(bytes32,bytes)", "0x30c0f8d6": "scheduleTransaction(address,bytes)", "0x69a5e902": "multiAccessCall(address,uint256,bytes)", "0x6f8b44b0": "setMaxSupply(uint256)", "0x919edc7c": "getChainySender(string)", "0x0b7ad54c": "getContent(uint256)", "0x5bfdc700": "registerData(address,int256,bytes,address)", "0x0d1fce42": "getBankroll()", "0x739b47ca": "recordWin(address)", "0xa5ea11da": "getParameters()", "0xf8af9e6f": "setAdv(uint256,string,string)", "0xe32e9f22": "setDeploymentReward(uint256)", "0x0baaaed9": "setConfigBytes(bytes,bytes)", "0x99f4b251": "mine()", "0x362af076": "createRequest(address[3],address,uint256[11],uint256,bytes)", "0x7fd238ba": "doCoinage(address[],uint256[],uint256,uint256,uint256)", "0x3adb2de7": "bet_this_spin()", "0xa311dd70": "setArray(uint8[10])", "0xc5bf339c": "getLastNonPublished()", "0x9d1bbd7e": "CancelRoundAndRefundAll(uint256)", "0x89790192": "WithFee(address,uint256)", "0x1c879c47": "getMarketHashes(bytes)", "0xbb84d362": "splitProfitVIP_only_Dev()", "0xffb1a6cb": "getWins(address)", "0x3b355af6": "baseData()", "0xb181a8fc": "resetContract()", "0x7d3d6522": "goalReached()", "0xd4c2b6b1": "scheduleTransaction(address,bytes,uint256[5],uint256)", "0xd65ab5f2": "startGame()", "0x4c4766e8": "KittenRegistry()", "0x77e5bf84": "getTxGasprice()", "0xff981099": "getVotes(uint256)", "0x4a7b26ec": "join_game(uint256)", "0xcccf7a8e": "has(uint256)", "0xa525f42c": "transferFromToICAP(address,bytes32,uint256)", "0xeef8e35f": "setChainyURL(string)", "0x557ed1ba": "getTime()", "0x595da94d": "has_owners(uint256)", "0x12511c14": "transferEnable(bytes20)", "0x2b291eb6": "UserAddTicket(bytes)", "0x50baa622": "withdrawToken(uint256)", "0xc01a8c84": "confirmTransaction(uint256)", "0x671dacdc": "CalculateSqrt(uint256)", "0xe74ffbd5": "getPart(bytes32,uint256)", "0xdd54a62f": "content(string)", "0x4025b293": "redeemAllOutcomes(bytes32,uint256)", "0xa8659216": "setInitialLockinDays(uint256)", "0x00b5b223": "computeResponse(uint256,uint16)", "0x2ef761d3": "buyTile(uint8,uint8)", "0x0a874df6": "lookup(uint256)", "0x42c69566": "get_address(address,string)", "0x02dc2e1d": "queuePayment(bytes)", "0x86bb7121": "getBlocksPerRound()", "0xacfdfd1c": "deploy(uint256,string,string,address)", "0x7d298ee3": "beforeExecute(address,uint256)", "0x5023d124": "TestFactory()", "0x827ef325": "_parseMsgData(bytes)", "0xd35b9d83": "codeAt(address)", "0x26161670": "donkeyRanking(uint256)", "0xe0834ea4": "WatchBalanceInEther()", "0xd44f2d3d": "getInitialWithdrawalDone()", "0x4f223fe3": "StatefulFactory(string,string,string)", "0x91cd242d": "setMeta(bytes32,bytes32,bytes32)", "0x9a97043b": "depositIdx(address)", "0x85db2dda": "PayoutQueueSize()", "0x423e1298": "setDoNotAutoRefundTo(bool)", "0xb7a97a2b": "isValidChannel(uint256)", "0xc1441172": "setBlackFlagRequest(uint256,uint256)", "0x53d9d910": "create(address[],uint256,uint256)", "0x64d905c0": "awaitingParticipants()", "0x718bd6dd": "setRequestUntil(uint8)", "0x5a353193": "KrakenPriceTicker()", "0xfb099c84": "newInvestor()", "0xd264e05e": "forward()", "0xcd9f05b8": "balanceEtherAddress(address)", "0xa1da2fb9": "retrieveDAOReward(bool)", "0x60708ae3": "issueAndCommit(address,address,uint256,uint256)", "0x109df68e": "rotateBitsRight(bytes,uint256)", "0x793cd71e": "cashOut()", "0xd3017193": "addUser(address,uint256)", "0xaacf5328": "setVideoID(string,uint256)", "0xb56e1bca": "setExchangeToken()", "0x9341231c": "sendOrThrow(address,uint256)", "0xaed8f3da": "partsPerBillion(uint256,uint256)", "0xdcff5581": "NewFeeAddress(address)", "0xbbe4fd50": "getNow()", "0x3df16377": "make_move_and_claim_victory(uint256,uint8,uint8,uint8,uint8,uint8,uint8,uint8)", "0x5ae5df8f": "deleteRef(string)", "0x6d853ab6": "isSubUser(address)", "0x28472c6c": "claimComputation(bytes,bytes)", "0x2c215998": "updateStatus(string)", "0x7eff1465": "setAccountAllowance(address,address,uint256)", "0xd5089396": "Token(string,string,uint8,uint256)", "0xd1f7a4e4": "createCertificate(bytes)", "0x8c0e2a31": "regProxy(address)", "0xa819819b": "sweepDeityCommission(uint256)", "0x2b861629": "storeBlockHeader(bytes)", "0x25d4bdeb": "LookAtCollectedFees()", "0x5dddea66": "updateState(uint256,uint8,uint256)", "0x3ccfd60b": "withdraw()", "0x6795dbcd": "getAddress(bytes32,string)", "0x9b9ba572": "oraclize_query(string,string[3])", "0xa925d85e": "Exchange(address,address)", "0xbfe8c107": "betOnDozen(bool,bool,bool)", "0x1af716ba": "transferFrom(address,address,uint256,string)", "0x67eae672": "sendCoinFrom(address,uint256,address)", "0x311d5a2a": "recordBalance(address)", "0x7ca823d5": "getAverageChainWork()", "0x19483cd1": "checkHash()", "0xd366fbab": "startLottery(bytes32,uint256,uint256,uint256,uint256,bool)", "0x4d70d1d7": "generateId(uint256)", "0xe13dc28b": "testValidTransfers()", "0x12065fe0": "getBalance()", "0xdd67a360": "OrderLifeCycle()", "0xd7c23572": "historyTimesPlayed(address)", "0x2675c123": "CloseContract()", "0x1381e400": "cancel(uint32)", "0xa48a663c": "transferFromToICAPWithReference(address,bytes32,uint256,string)", "0xb03260be": "scheduleTransaction(uint256,address,bytes)", "0xb37217a4": "getRandomNumber(uint256)", "0x5c54305e": "InsufficientFunds(address,uint256,uint256)", "0x17e1b09b": "minimumDeposit(uint256)", "0x10c4e8b0": "all()", "0xa31d5580": "Registrar(address,bytes32,address)", "0xbe6307c8": "getDraw(uint256)", "0xc985c221": "get_all_levels()", "0x91b7f5ed": "setPrice(uint256)", "0xe42def21": "CryptoHill()", "0x738486bd": "BeerCoin(uint256)", "0xe422ebe9": "getBot()", "0x67dd74ca": "buyTicket(uint256)", "0x276b94e1": "copypaste()", "0x39aaba25": "get_status()", "0x7ed19af9": "multiAccessRevoke(bytes32)", "0x4c1b2446": "transmitInteger(address,bytes,bytes,uint256,uint16)", "0xd014c01f": "enter(address)", "0x1d49e081": "EXECUTE_EXTRA_GAS()", "0x9dafbc13": "initBlock(uint256)", "0xc7e22ac4": "setOracleGas(uint256)", "0xa3053236": "SafeInvestments()", "0xf42ac1de": "minQuorum(uint256)", "0x04d91c6a": "testFail()", "0x0e662cf0": "buyTokens(uint16)", "0x1ef0625b": "player_2(uint256)", "0xcec1365a": "ShortLimit(uint256)", "0x340f5e4e": "get_all_num_levels()", "0x3e2729bf": "isRevocated(bytes)", "0x5a1cc358": "getChannelRank(address,uint256)", "0x4d366398": "runPeerBalance()", "0xaf9a3f9b": "hashName(string)", "0x33298e25": "invoke(uint256,uint256)", "0x63def590": "untrustClient(address)", "0x836d6d66": "WeeklyLotteryB(address,uint256)", "0x7f497550": "scheduleTransfer(address,uint256,uint256)", "0xf9e05ed9": "sha(uint128)", "0xf6458c6a": "toZ1(uint256[3],uint256)", "0xf41017fc": "finalize(uint24)", "0xeeb57139": "CollectMoney(uint256)", "0xfdacd576": "setCompleted(uint256)", "0xb7266456": "StandardToken()", "0x6a8c2437": "totalRescues()", "0x1fdf6e0c": "protectKingdom()", "0xcf31e9fe": "getOutputHash()", "0xc8117b5b": "extractBalanceOfLength()", "0x0674763c": "assert(bool)", "0x87def081": "getFeeRecipient(int256)", "0xc63ff8dd": "claim(bytes)", "0x329bfc33": "getCurrentWinner()", "0x3d6a3664": "setNewOracle(address)", "0xdaa21e0e": "testBitSetSuccess()", "0xbf8783e0": "callAndGetReturn(address,bytes,uint256)", "0xb06df18e": "transfer(bytes20,address)", "0x00100a18": "NewPoll(string,string,uint256,uint256)", "0x2ffb8631": "getReleaseLockfileURI(bytes32)", "0x6716a692": "setDVIP(address)", "0xe8223468": "sha3clone(bytes)", "0x0aeacb5e": "getTotalRecords()", "0x29e30910": "testThrowCreateExistingNonce()", "0x240ecad5": "transferViaProxy(address,address,uint256)", "0xa33d4968": "Tripler()", "0x8caaaae6": "totalWeiPrice()", "0xf28a7912": "quick2()", "0xcbe9ef39": "BasicCoin(uint256,address)", "0xea3d2827": "selectWinner(string)", "0x92e9fd5e": "ColdWallet(address,address)", "0xcd5e3c5d": "roll()", "0x4a82534b": "create(address,address,address,uint256,uint8,uint8,uint256)", "0x5ccd2f9b": "_deleteAllPackedRevisionBlockNumbers(bytes20)", "0x9380b8e7": "testFailAddingMembers()", "0x31b0795c": "registerAddress(address,address)", "0xb76e4890": "Tester()", "0x29c08ba2": "payPremium()", "0xd7f31eb9": "forward(address,uint256,bytes)", "0xd7ef1356": "best_adjustment(bool)", "0x48d9a374": "blockTransfer(address,uint256)", "0x88b9e10e": "seizeTokens(address,uint256)", "0x8736fd16": "getRefStatus(uint256)", "0x2b30d2b8": "invoke(uint256)", "0xd4e78272": "Draw()", "0x0257c48c": "meta(bytes32,bytes32)", "0xc1246d39": "simulatePathwayFromBeneficiary()", "0x699b328a": "randomize()", "0xa200dc73": "getNextShareholder(address)", "0x9a7a7c11": "makeRoll(uint256)", "0x2bf1f9da": "restart(bytes32,bytes)", "0x943a32bc": "Relay(address)", "0x93503337": "isAllowed(bytes32,uint256)", "0xe97b2190": "add_wall(uint16)", "0x0448f79f": "addOptionChain(uint256,string,uint256,uint256,bytes,address,int256[])", "0xc0df77d0": "getRefName(uint256)", "0x27121069": "verify(bytes,uint8,bytes,bytes)", "0xb7009613": "canCall(address,address,bytes4)", "0x0295d71b": "currentDepositLimit()", "0x35ee2783": "Alarm()", "0x71b6663e": "play1(address,uint256)", "0x0178fe3f": "getData(uint256)", "0x489306eb": "oraclize_query(string,string)", "0xfce59d0c": "MangoRepo()", "0x8efc777f": "isBeta(bytes)", "0x1f7b8622": "getVotingDeadline()", "0x76da5667": "admin_kill()", "0x152cf9db": "getDataPoint(int256,uint256,uint256)", "0xd09de08a": "increment()", "0x64ee49fe": "scheduleCall(address,uint256,bytes4,uint256,uint256,uint8)", "0xd9fe60f3": "DTHPool(address,address,uint256,string,string,string)", "0x6b4dd158": "getPrice(bytes)", "0x0a80ef45": "getIsClosed()", "0x51fdaf92": "checkExpiredfunds()", "0x694e0d5b": "StringPasser(uint8[])", "0x0e1d88fc": "addTender(uint256,uint256,address,uint256)", "0x53850db3": "getParticipantById(uint256)", "0xb6cb405b": "getContractor()", "0x4c6b25b1": "results(bytes32)", "0x1c4e6cd0": "NameReg()", "0x53aab434": "buyIn()", "0x1ebe5c0f": "sendWithAllOurGasExcept(address,uint256,uint256)", "0xd98b9bb5": "placeBid(address,uint256)", "0x02e8d8c0": "scheduleTransaction(address,uint256,uint256)", "0x8a120dc9": "testBitEqualFailIndexOOB()", "0x33f707d1": "ownerWithdraw(uint256)", "0x98e00e54": "getCallWindowSize()", "0x4da74ee6": "setVoteIntention(uint256,bool,bool,string)", "0x6617e11a": "NiceGuyTax()", "0xfe13a823": "computeResponseFirstHalf(uint16)", "0xf7bd2361": "LookAtBalance()", "0xb09bc3bf": "try_to_get()", "0x0cee22e9": "testSetBalanceSetsSupply()", "0xae404996": "oraclize_query(string,string[3],uint256)", "0x2ad95786": "winner(address)", "0xe5fe4f31": "buy(uint8,bytes32,bytes32)", "0xe23941bc": "testDepositWithdraw()", "0xfc89aff6": "submitVerifiedUsers(address[])", "0xddf187b0": "dogFight()", "0xb5f5962a": "CALL_GAS_CEILING(uint256)", "0x92093dd6": "getLastResult()", "0xbfad16f4": "new_offer(uint256,uint256)", "0x01df7f30": "validateProposedThroneConfig(uint256,uint256,uint256,uint256)", "0x4054f5de": "EthVentures3()", "0x244c23ee": "Token(uint256,string,uint8,string)", "0xc3daab96": "withdrawBond(uint256)", "0x5fa21f1f": "enableBetting()", "0x3b591ea7": "AmountToForgeTheNextBlock()", "0x8c3c4b34": "getSaleStatus()", "0x7429f1eb": "multiAccessSetRecipientD(address,address)", "0xf99ff4df": "paged(uint256,uint256)", "0x2a64fb63": "getSaleDate(bytes)", "0x749f9889": "changeAllowedRecipients(address,bool)", "0x053c351b": "oraclize_getPrice(string)", "0x19663f7f": "TransferAmountFromBankAccountToAddress(uint256,address)", "0x5292c1a9": "testThrowsRestartEnforceRevisions()", "0x68f2ab8e": "Currency(string,string)", "0xd6e0bf29": "OwnerDeposit()", "0x94c3fa2e": "getLastBlockHashUsed()", "0x45362978": "query1(string,string)", "0xaff21c65": "getMinimumEndowment(uint256)", "0xe33c7ae2": "scheduleTransaction(uint256,uint256,bytes)", "0x9eb9dd3b": "getBetsProcessed()", "0x3807ba1b": "poi()", "0x7281854d": "GetCategoryValue(uint8)", "0x45ee49b9": "getUltimateOutcomes(bytes)", "0x0109f22e": "CrowdSale()", "0x98596726": "note(uint224)", "0x06900c41": "ZeroPonzi()", "0x3df76482": "fipsPublishData(bytes20,bytes)", "0xe0429b6c": "ShinySquirrels()", "0xa4a7cf5c": "redeemWinnings(bytes32)", "0x2e898ddc": "validateTemporalUnit(uint256)", "0x3af75ee1": "storeBlockWithFee(bytes,int256,bytes,int256)", "0x43e6125d": "Badge(address)", "0x75a6a332": "testThrowRetractNotRetractable()", "0xbed411a0": "CheckPrize(address)", "0x16f3cb5e": "__kill()", "0xe8efc1a0": "updatedValue(bytes32)", "0x9c7264d7": "fillOrder(address,uint256)", "0x9a0af2ec": "getStLength()", "0xf62cce34": "_clearRecordHierarchy(uint256,bytes32[],bytes32)", "0x940f851c": "Ballot(uint8)", "0xd96e5565": "testThrowsRetractNotRetractable()", "0x3a314b24": "SendETH(address)", "0xbd8c1d33": "checkTransferFromToICAPWithReference(address,bytes32,uint256,string)", "0x01da73ff": "isValidChannel(bytes)", "0x8f8bde82": "MicroDAO()", "0x2973e372": "isAlphaUpper(bytes1)", "0x1d2b7155": "activateImportFeeChargeRecord(address)", "0x06ab5923": "setSubnodeOwner(bytes32,bytes32,address)", "0x9d7d6667": "multipliers()", "0x8af49ab7": "maintain(uint256,uint256)", "0x1f3a3a53": "mint(int256,uint256)", "0x74389991": "breakit()", "0x64371977": "set(uint256,string)", "0x3fa6497f": "AdminAddFunds()", "0xba7dc45f": "_removeOperation(bytes32)", "0xf81d087d": "prepareLottery()", "0xd239ea8b": "getSchemasLenght()", "0xa2f3ede2": "computeNameHash(bytes)", "0xa28ecf0b": "sendCryptedHand(bytes)", "0x003b9d88": "setLowerFeePercentage(uint8)", "0x98ea5fca": "depositEther()", "0xb9a0a708": "testChargesAmountApproved()", "0x55291dbd": "claimEther()", "0x2d2800f1": "react()", "0xa9d2293d": "lastClaimBlock()", "0xc45aa04c": "queryShareholders(bytes,uint256)", "0x67af1c81": "getRoundIndex()", "0x50b7b7a2": "setRating(bytes32,uint256)", "0x0aa7881a": "MintableToken(int256,uint256)", "0x0eb3f5a0": "sweepCommission(uint256)", "0x97d47a60": "registerAccountant(bytes,address)", "0xe2c61114": "setImportFee(address,uint256)", "0x6dbe31eb": "testSubBalance()", "0xf5c98aff": "GreeterB(bytes)", "0x79216f5f": "add_monster(uint16,uint16,uint16)", "0x023c23db": "getSize(uint256)", "0x0e1ca8a5": "Oraclize()", "0xa05e822a": "howManyOwners()", "0x313b7b19": "finance()", "0x51a5f2f2": "ConsultingHalf(address,address)", "0x1fb6e99d": "paymentNeeded(uint64)", "0x2bffc7ed": "add(string,address)", "0x5c52c2f5": "resetSpentToday()", "0x39cdde32": "ecverify(bytes32,bytes,address)", "0x64e24f4b": "UpdateClientTokenAccount(address)", "0x0d2560ee": "addMe()", "0xd8589be3": "CoinFlipper()", "0x3b46a7df": "ivote(bool)", "0xa6823189": "parseAddr(string)", "0xd0c24e93": "setNotUpdatable(bytes20)", "0x1f13de92": "inEther(uint256)", "0xb6ce5581": "oraclize_query(string,string[5],uint256)", "0x31ae0019": "KissBTC()", "0xdabf7dc8": "PayoutDividendEarly(uint256,bool)", "0xe3da41b5": "sortWinningNumbers(uint8[5])", "0x8ea98117": "setCoordinator(address)", "0xeff6be2f": "changeBaseFee(uint256)", "0x483a83df": "setKYC(address)", "0xf98a4eca": "executeVote(uint256)", "0x776d1a01": "unvest(uint256,uint256,uint256,uint256,uint256,bool)", "0x1cf52f2b": "isActiveRegistrant(address)", "0x24c9bf5e": "Prizes()", "0xb3822da8": "getContents(uint256[])", "0x999a9965": "setMany(uint256,int256,uint256,bytes,address,bytes)", "0xbade6033": "propose(bytes,uint256)", "0xd38159b8": "testPass()", "0xdabf7ec4": "helper(uint256)", "0xf4993bbd": "executeEmergencyWithdrawal()", "0x46ddb7db": "setAccountBalance(address,uint256)", "0xc12af1ce": "fipsRegister(uint256,bytes)", "0x8f420866": "DEFAULT_SEND_GAS()", "0x62770252": "needsFuneral(uint256)", "0x32921690": "checkDepth(address,uint256)", "0xb3c1a588": "parseMsgData(bytes)", "0x29cd5777": "_tryEraseSingleNode(bytes32)", "0xc6e0c908": "checkTransferFromWithReference(address,address,uint256,string)", "0x9bd99195": "multiAccessChangeOwner(address,address)", "0xa360b26f": "Migrations()", "0x6c6f1d93": "getContractCreationValue()", "0x5445e38c": "_isCycleValid(uint256)", "0x2c85f8e0": "oraclize_query(string,string,string,uint256)", "0xcdda62ad": "FutureBlockCall(address,uint256,uint8,address,bytes4,bytes,uint256,uint256,uint16,uint256,uint256)", "0xc8fdc891": "numberOfMonarchs()", "0xf578fd85": "assertEq0(bytes,bytes)", "0xda311588": "getCoin(uint256)", "0x9a35f886": "__dig_then_proxy(uint256)", "0xfa14df6b": "getChangeRecipientFee()", "0x95f0684b": "getPackageNameHash(uint256)", "0x42909a9e": "create_game()", "0x51582ef3": "sendProxyTransaction(address,uint256,uint256,bytes)", "0xc4e41b22": "getTotalSupply()", "0x110df916": "getChannelID(uint256)", "0x4dc3141b": "CalcAll()", "0xb88a802f": "claimReward()", "0x82b2e257": "getTokenBalance()", "0x9bb0e4df": "getUint(int256,bytes32,string)", "0xc1ae4044": "checkBetColor(uint8)", "0x4a8b5389": "allocateBountyAndEcosystemTokens()", "0xdf06f906": "numBets()", "0xdeb80111": "transfer_asset(address,uint256)", "0x5216aeec": "totalInvested()", "0xe2deaa81": "set_reference(uint256,uint256,uint256)", "0x2ffda1e0": "setBlackflag(uint256,bool)", "0xba45b0b8": "transfer(address,address)", "0x7d7c2a1c": "rebalance()", "0xf32efd3c": "recoverUser(address,address,uint256,uint8,bytes32,bytes32)", "0x4571d4c4": "FutureCall(address,uint256,uint16,address,bytes,bytes,uint256,uint256,uint256)", "0x5fc5d48b": "burnUnsoldCoins(address)", "0x4da47ba0": "TokenSale(address,uint256)", "0x3d9ce89b": "scheduleCall(bytes4,bytes,uint256)", "0x6662e4be": "isWinningBet(uint256)", "0xa501e88d": "Content()", "0x4b70cec4": "getTime(address)", "0x12253a6c": "stopContract()", "0x173825d9": "removeOwner(address)", "0x26121ff0": "f()", "0x7b647652": "LittleEthereumDoubler()", "0x0c5c2ca3": "getIndexName(bytes)", "0x90f2c86d": "convertToWei(uint256,string)", "0x83f95f13": "openClaim(string)", "0xe8b5e51f": "invest()", "0xfdc4b338": "authorizeExtension(uint256,bool,string)", "0x4e116eb8": "unRegisterCertificationDb(address)", "0x5c8a1053": "extend(string)", "0xa932ed0d": "whitelistRemove(address)", "0xa1188e56": "getCurrentDifficulty()", "0xbf1fe420": "setGasPrice(uint256)", "0xead710c4": "greet(string)", "0x144fa6d7": "setToken(address)", "0x42402c2c": "fipsTransferMulti(bytes20[],address)", "0x93e84cd9": "play()", "0x741b3c39": "depositBond()", "0x23306ed6": "getMinimumBond()", "0x5f2e686d": "Ethereum_eight_bagger()", "0x9890220b": "drain()", "0x233120aa": "getChainyURL()", "0xe9dc0614": "vote(bytes)", "0x4df53a0f": "testSetApprovalDb()", "0x5fe27ab0": "createHKG(address)", "0xb56b2627": "add_owner(uint256,address)", "0x6f9a023c": "theultimatepyramid()", "0xc8796572": "collectFees()", "0xea3ebae6": "getConfigBool(bytes32)", "0x213ac932": "addUser(address,uint256,uint8,bytes32,bytes32)", "0xfae9d06d": "calculateTxFee(uint256,address)", "0x45d27edf": "forward_method(bytes,address,uint256,bytes)", "0x8e9ccd04": "computeIndexId(address,bytes)", "0x2f6ae467": "transferDocument(bytes,address)", "0x6e353a1d": "emergencyWithdrawal(address)", "0x7dd56411": "ownerOf(bytes32)", "0x9dc35799": "updateReading(uint256)", "0x246c02e6": "check_depth(uint16)", "0x03d22885": "scheduleCall(address,uint256,bytes4,uint256,uint256,uint8,uint256)", "0x881be8f7": "undo()", "0x953307d8": "revealScissors(string)", "0x7af30442": "testToggleBitFailIndexOOB()", "0x1e0c7ed4": "setConfigBool(bytes32,bool)", "0x2e3be78d": "setPrecisionDirect(uint8)", "0x95a078e8": "hasAccess(address)", "0x245a03ec": "scheduleSetIt(uint256,uint256)", "0xb863bd37": "random(uint256)", "0xa5e62f02": "fallbackRP()", "0x618fa9ce": "getBotBillingIndex(uint256,uint256)", "0x06909f69": "cancel(string,uint256)", "0x2b198366": "addCertifier(address)", "0x57e871e7": "blockNumber()", "0x2b98222e": "getInstitutionByAddress(address)", "0x89eedf00": "setPdfHash(bytes,bytes)", "0x7ac4b05e": "returnMyMoney(uint256)", "0x7fc90182": "Pool(uint256)", "0x291e6777": "sendVote(uint256,uint256)", "0x579badf6": "UniversalFunction(uint8,bytes32,bytes32,bytes32,bytes32,bytes32)", "0xe1f21c67": "approve(address,address,uint256)", "0xa1b9af31": "unlockBets()", "0x92d0d153": "t()", "0x095ea7b3": "approve(address,uint256)", "0x9cb31079": "setLowLimit(uint256)", "0xb971b4e5": "setNotTransferable(bytes20)", "0x2a745971": "BlockKing()", "0x582ca57b": "get_associations()", "0xa0d605c6": "addCertificationDocumentInternal(address,bytes32)", "0x85233869": "NumberOfMiners()", "0xb0349184": "clearRecords(bytes32[])", "0x3e8f5b90": "setConfig(string,uint256)", "0x7c73f846": "getMinimumEndowment(uint256,uint256,uint256)", "0xcc9ae3f6": "getMyReward()", "0xac900c2d": "unregisterSeller(address)", "0x306df22d": "GPSDestination(int256,int256,uint256)", "0x5e968a49": "ownerSetMaxProfitAsPercentOfHouse(uint256)", "0x80dcaf27": "getRefNumber()", "0x4245b0f7": "Lottery()", "0xe46164c5": "waitingForPayout()", "0xa8c3ec48": "oraclize_query(uint256,string,string[2])", "0x58ea80e5": "setThroneCreationPrice(uint256)", "0x8a4fb16a": "getWithdrawal(uint256)", "0x4464aec7": "testTryGet()", "0xf3c37bd5": "Verifier(address,uint256,uint8)", "0xb4c4e005": "testTransferToAcceptAuthority()", "0x346b306a": "oraclize_query(string,string,string)", "0x24032866": "checkExecutionAuthorization(address,uint256)", "0x509f8633": "create_account()", "0x26da8e17": "ownerUpdateCostToCallOraclize(uint256)", "0x74a93e6c": "setTokenHolder(address,address)", "0x1baaeb91": "getSignature(bytes4,uint256)", "0x337c1e28": "getIndexRoot(bytes)", "0xac92fdb5": "getSaleDate(bytes16,uint256)", "0x13b2663b": "cash_received(string)", "0x68e4bd99": "testSetBitSuccess()", "0x20620f37": "onAuctionEnd(string)", "0x85528394": "currentClaimPriceWei()", "0x1995333b": "burnFunds(uint256)", "0x5184ffc9": "setAmbiAddress(address,bytes)", "0x269975d0": "GameDetails(uint256)", "0x29274fe1": "buyBOTx(uint256,string,string,address,uint256)", "0x720c4798": "workshop()", "0x6f9a5eab": "createTx(uint256,address,uint256)", "0xb66a323c": "claimThrone(string)", "0x14cabddb": "joinProof(uint256)", "0x76e4ca0d": "voteQuorum(uint256,bool)", "0xbcb3b5d2": "getGamblesList(uint256)", "0xf2080ba6": "Pong(int8)", "0xab470f05": "getCaller()", "0xe0b1cccb": "updateBalance(address,uint256)", "0x4889ca88": "receiveApproval(address,uint256,address)", "0xbc4b3365": "addFunds(address,uint256)", "0x5d1a3b82": "getOutcome(bytes32)", "0x8204ecdd": "getFee(bytes)", "0x49aa4ee2": "removeVote()", "0x9131d803": "testSetFrontend()", "0x72929b56": "getKudosPerProject(address)", "0x287418e7": "query(uint256,uint16)", "0xbb7859b5": "periodThree()", "0x7dc5cd32": "_patternToNumber(bytes)", "0xf7ea7a3d": "setTotalSupply(uint256)", "0xb3f98adc": "vote(uint8)", "0x655388be": "walkTowardsBlock()", "0x856deacf": "findTag(string)", "0xcaab0acc": "testThrowCreateRetracted()", "0xede8ebf3": "checkApprove(address,uint256)", "0x4296a9cb": "getNodeRightChild(bytes)", "0x03cf4fd6": "expire(uint256,uint256,uint8,bytes32,bytes32,bytes32)", "0x87045369": "setCanCall(address,address,bytes4,bool)", "0x0ed21029": "getIssueAssignee(uint256,bytes32)", "0xc5958bda": "removeFile(bytes)", "0xa8026912": "setSource(address)", "0x05a5b8c6": "verifyTx(bytes,int256,int256[],int256,bytes,int256,int256[],int256)", "0xed3058e0": "transferRight(address,bytes)", "0xba487e62": "newCampaign(uint32,uint96,uint16,uint16)", "0xd6c19fe0": "build(bytes,uint256,uint256,address)", "0xebb741cb": "getChannelSize(uint256)", "0x3b996f40": "quarter(uint32,uint32,uint32,uint32)", "0x0b7373d6": "giveAllBack()", "0x3f887fad": "buyShares(uint256,uint8,uint256,uint256)", "0xb5deeca7": "BaseRegistry()", "0x6dd6e87b": "checkOut(int256)", "0x90a85119": "checkBetResult(uint8)", "0x724121ae": "contentExists(uint256)", "0x67bd69a6": "getLastDuel2()", "0x821e9169": "testFailControllerChargeMoreThanApproved()", "0x083ae1fe": "setPackage(string)", "0x8eaa6ac0": "get(bytes32)", "0xf42aa287": "getBlobStore(bytes12)", "0x32a2c5d0": "getContractAddress()", "0x69d89575": "releaseFunds()", "0x984ac378": "lotteryTitle()", "0x3baf4e1e": "newPayment(uint256,uint256)", "0x938c4307": "scheduleCall(bytes4,bytes,uint16,uint8,uint256,uint256,uint256,uint256,uint256)", "0x47372325": "getChannelSize(address)", "0xcb10e0c5": "getLastDuel1()", "0x1a0919dc": "unregister(bytes32)", "0x6a3c1198": "_projectCancelNew()", "0x976b01c0": "setNotRetractable(bytes20)", "0x44d03ac6": "BlockhashFetch(address)", "0xb0fd935b": "registerCertificationDb(address)", "0x2c7c4549": "PurchasableToken(uint256)", "0x3e0a51b4": "TweetAccount()", "0xc5f310c0": "register(bytes12)", "0xc10dd4c6": "getEvents(bytes32[],address)", "0x37a6b9f8": "recordCallback(address,uint256,bytes,bytes)", "0x1b9265b8": "pay()", "0xc91d7e9c": "getFee(bytes32[])", "0xf6c5c80d": "cleanUp()", "0x590e1ae3": "refund()", "0x9c709343": "split(bool,address)", "0xe68d3ae3": "escrow(uint256,string,address,uint256)", "0xa1cb31b7": "_state()", "0xfbac3951": "isBlocked(address)", "0x29dfdded": "addNewDonkey(address)", "0x7bc49a95": "play(uint256,uint256)", "0x5329c681": "checkTimeout(uint256)", "0x7c05caf3": "testCreateCostAuth()", "0x8606f905": "balanceOf(address,bytes)", "0xbe45fd62": "transfer(address,uint256,bytes)", "0xd7130651": "getCity(uint256)", "0xb2353d69": "updateRightLottery(address)", "0x01bb85a4": "__startBlock(string)", "0x4316abbb": "newJester(address)", "0x5c3e426c": "adminRetrieveDonations(address)", "0x293ffca3": "AddressReg()", "0x4156fdb7": "createSwap(uint256)", "0x2852b71c": "accept()", "0x027a5e3f": "getLastVersion(bytes)", "0x3d90d44d": "addPowerSource(address,uint256,uint256)", "0x1ab06ee5": "set(uint256,uint256)", "0x50f07cf9": "setReadingDelay(uint256)", "0x669e48aa": "get(uint256,uint256)", "0xdea9c72b": "getLatestPreReleaseTree(bytes32,uint32,uint32,uint32)", "0x60f66701": "useCoupon(string)", "0x2431f164": "process_payment()", "0x17623e5b": "unauthorizeManager(address)", "0x367bbd78": "strlen(string)", "0xbab86ea8": "test(string,string)", "0x25f3da52": "GetBankAccountNumber()", "0xebb045fa": "PublicResolver(address)", "0xdd57d5c5": "setTrust(address)", "0x22e803c2": "transferBounty()", "0x12c8052f": "won()", "0x3535cd52": "setDailyCosts(uint256)", "0x92d8c8cf": "setupImportFee(address,uint256)", "0x53caf582": "testThrowSetNotUpdatableNotOwner()", "0xb3ade772": "shipProducts(string,string)", "0x61472fd4": "CSGOBets()", "0x5c7c9aa4": "checkAccountState(address)", "0x6560a307": "suggestedGas()", "0xd0d552dd": "setAsset(address)", "0xc02f081a": "shiftBits(bytes,int256)", "0x9348cef7": "reveal(uint256,uint256)", "0x5ca1c5a0": "getNodeValue(bytes)", "0x34c1b4ba": "sha(bytes)", "0xe0117441": "setRegistrationPrice(uint256)", "0x92698814": "reserved(bytes32)", "0xaa1e84de": "hash(bytes)", "0x2125b65b": "transfer(uint32,address,uint224)", "0x9f9eac67": "ChangeName(string)", "0x7eb69ba1": "hint(int256,bytes32,string,bytes20)", "0xc6888fa1": "multiply(uint256)", "0xa9059cbb": "transfer(address,uint256)", "0x84c344fe": "_register(bytes4,string)", "0x744d8b4f": "recordWin(uint256,uint256)", "0xad1ef61e": "donkeyInvested(address)", "0xb4787dc5": "linkEID(bytes,bytes)", "0x7b1cbb13": "getChannelValue(bytes)", "0xd0febe4c": "buyTokens()", "0x74f519db": "setLastTimestamp(uint256,uint256)", "0xe30081a0": "setAddress(address)", "0x6fd902e1": "getCurrentBlockNumber()", "0x25d8dcf2": "betAndFlip()", "0xc062f578": "updateStage()", "0xe0cfc05c": "testThrowsRetractLatestRevisionDoesntHaveAdditionalRevisions()", "0x854f4817": "buyKissBTCWithCallback(address,uint256)", "0x58b1f29c": "refundBounty(uint256)", "0x0645b5d5": "getMyShareholderID()", "0xe9c63b9c": "requestPeerBalance()", "0x1b03316f": "getSecond()", "0xbcc6092a": "MyEtherBank()", "0x1cf43b63": "extractExportFeeChargeLength()", "0x5e58f141": "shares(address,bytes,int256)", "0x6103d70b": "withdrawPayments()", "0x3e0663e0": "AdminDrawProcess()", "0xa0f029fc": "ContractorInterface(address,address,address)", "0xe604cf9f": "get_all_squares()", "0xd13d1ace": "scheduleCall(bytes,bytes,uint16,uint8,uint256,uint256,uint256,uint256,uint256)", "0xb74bc710": "LuckyDoubler()", "0x611f69de": "__proxy_motion(address,uint256,uint256,bytes)", "0xbb814e9e": "versionExists(bytes32)", "0x545e7c61": "deploy(address,address)", "0x1b437d0c": "compareLastCalldata(bytes)", "0x845051d3": "testContractsNotNull()", "0x23de6651": "emitTransfer(address,address,uint256)", "0xd78c20ff": "voteApprove(uint256)", "0x6f13e01b": "EthVenturePlugin()", "0x1f6b0a9d": "getReleaseLockfileURI(string,uint32,uint32,uint32,string,string)", "0x13d4bc24": "buyTokenProxy(address)", "0xd509b16c": "testWithdraw()", "0x2f54bf6e": "isOwner(address)", "0xf60381a1": "stra2cbor(string[])", "0x34dbe44d": "getLastBlockNumberUsed()", "0x28dcfdac": "getSignsCount(uint256)", "0x2888f9d0": "updateMaxBet()", "0xc3b2556d": "lookup(bytes)", "0x6fbaaa1e": "currentMultiplier()", "0xe241c1d9": "deriveKey(uint256,uint256,uint256)", "0x5b37e150": "create(bytes32,bytes)", "0xa1c95ac2": "GSIToken(uint256,string,uint8,string,address)", "0xfc9e53df": "setNextRegistrar(address)", "0x34e8980f": "bootUpHangouts()", "0x457dd8b3": "setMasterKey(address)", "0xdb833e3a": "sellShares(bytes32,uint8,uint256,uint256)", "0x1e5330ca": "checkBetResult(uint8,address,bytes32,bytes32)", "0x4a00a522": "homebase(int256,int256)", "0xb938bf42": "sendBounty(bytes32)", "0x46be96c3": "amountFilled(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32)", "0x2fa00e58": "fipsTransfer(bytes20,address)", "0x95669952": "debtor(address,uint256)", "0x3f19d043": "getContributions(address)", "0xeb455dc6": "sendBitcoin(string,uint256)", "0x034cb28e": "addressOf(address,bytes)", "0x1e26fd33": "setBool(bool)", "0x1b55ba3a": "Start()", "0x6f3fe404": "updateBalances()", "0xc45b415e": "createRequest(address[4],address,uint256[11],uint256,bytes)", "0x180aadb7": "underLimit(uint256)", "0xb44bd51d": "getConfig(string)", "0x93d79105": "hashRelease(bytes32,bytes32)", "0x9f8a13d7": "isActive(address)", "0x257bcd6a": "placeBet(uint256,bytes32,bytes32)", "0xdd10d97e": "getPlayerWaiting()", "0x6d16f79c": "__transferWithReference(address,uint256,string)", "0x6ce1417e": "Fund()", "0x67beaccb": "scheduleCall(bytes)", "0x4b0697e4": "Manager(address)", "0x6a7fc8b7": "setDailyWithdrawLimit(uint128)", "0x7a6e9df7": "getTimestamp(bytes)", "0x797af627": "confirm(bytes32)", "0x81183633": "setStandard(bytes32)", "0x6a1db1bf": "changeFee(uint256)", "0x4e6ab570": "insert_order(address,bool,uint32,uint128)", "0xa4e2d634": "isLocked()", "0x7c79ebce": "expired(uint64)", "0x20965255": "getValue()", "0xfd408767": "fireEventLog4()", "0xcf6b3822": "WatchCollectedFeesInSzabo()", "0x8f7fe231": "ValidetherOracle()", "0xbf12165e": "fillUpSlot(uint256,uint256)", "0xdabdc1f2": "ChangeActiveDigger(address)", "0xe9e7a667": "get_stake(bytes32)", "0x0ad95b44": "bribery()", "0xdb6fcf01": "is_destroyed(uint256)", "0x4378a6e3": "getAttributes(uint256)", "0x71589d6b": "newponzi()", "0x47274dbe": "disableUser(address,address)", "0xb40a5627": "bidCount()", "0xf1eae25c": "mortal()", "0x13af4035": "setOwner(address)", "0xaf030d2c": "setResult(uint256,uint256,bytes32)", "0x098ab6a1": "snapshotCount()", "0x27cca148": "lastClaimedBlock()", "0x940c154b": "lockBet(uint256)", "0x378c0605": "buyTickets(address)", "0xbcfcb03e": "allocateFounderTokens()", "0x0138e31b": "_jAdd(uint256,uint256,uint256,uint256)", "0x0a7f4239": "getAccountFundContract(address)", "0xc96593a0": "The10ETHPyramid()", "0x22beb9b9": "scheduleDoIt(uint256)", "0xdb0e127a": "openDoor()", "0x3dc02266": "fipsRegister(uint256)", "0x7d242ae5": "setBasePrice(uint256,bytes)", "0xe82b7cb2": "proxySetCosignerAddress(address,bytes32)", "0xa60bbcd3": "ModelCoordinator()", "0xc26aa3c9": "lockUnicorn(uint256)", "0x96ff7e97": "requestIdentity()", "0x99753de7": "clear_level()", "0x69d79ad5": "moneySumAtSettlement(address,uint256,uint256,int256,uint256,uint256)", "0xda359dc8": "setBytes(bytes)", "0x6edb4cf6": "testThrowRetractLatestRevisionDoesntHaveAdditionalRevisions()", "0x9d170c5d": "getRef(string)", "0x11cd98ed": "convertToAllTable(uint256,string)", "0x67f809e9": "DynamicPyramid()", "0xd5f37f95": "sign(uint256,uint256,address)", "0xf5562753": "getClaimAmountForBlock(uint256)", "0xc9bbc8c0": "donkeyName(address)", "0x5858ef10": "testErrorNonOwnerCantBreach()", "0x74388347": "checkBetDozen(uint8,address,bytes32,bytes32)", "0xee564544": "_slotCancelNew()", "0xf3bb9741": "commitmentCampaign(uint256,bytes32)", "0x2b68b9c6": "destruct()", "0xa9b8f7b8": "ProtectTheCastle()", "0x16181bb7": "shortSellShares(bytes32,uint8,uint256,uint256)", "0xb524abcf": "totalSupply(bytes32)", "0x8006745b": "getPayout(address)", "0x137c638b": "getExtraGas()", "0x824d5603": "getIndex(uint16,uint16)", "0x245a6f74": "isProxyLegit(address)", "0x9eded57a": "paybackLast()", "0x7b1aa45f": "ownerDeposit()", "0x974654f4": "requiredGas()", "0x76d690bb": "BountyList()", "0xf4b2dfea": "Matching_Finneys()", "0xbd66528a": "claim(bytes32)", "0x85eac05f": "changeOwnerAddress(address)", "0xa69df4b5": "unlock()", "0xe6d9bb0f": "secondsUntilEnd()", "0xcd57a448": "SwapContract(address,uint256)", "0xb245fc92": "findNextMonth(uint256,bytes)", "0x7620f4bb": "fipsNotaryLegacy68b4()", "0x61886014": "combineDice(uint8,uint8)", "0xdf4ec249": "step3()", "0x2262cd94": "wroom()", "0x1099d3ec": "scheduleTransaction(uint256,uint256,uint256,bytes)", "0xd8c34127": "isKnownSignature(string)", "0x8afa08bd": "setDrawDate(uint256)", "0xdb18c972": "play4(address,uint256)", "0x2f30283e": "testSomething()", "0x8ca17995": "divest(uint256)", "0x1ef3755d": "restart()", "0x99bb875c": "funeralAndBirth(bytes,int256,bytes)", "0x157ad5a1": "canWithdrawBond(address,uint256)", "0xfd8055d2": "updateBOTBillingInfo(uint256,string,address,string,string,uint256)", "0xa10889fa": "setVersion(uint32,uint32,uint32,string,string)", "0x51cff8d9": "withdraw(address)", "0xbe999705": "addFunds(uint256)", "0x2e5d1042": "requestPayout(uint256,uint256,bytes32,uint256,uint256)", "0xb50954b6": "cancelWaitingForOpponent()", "0xc42cd8cf": "etherSplit(address,address)", "0x42ce1488": "upload(string)", "0xad04592e": "owner_deposit()", "0xc2cf7326": "hasConfirmed(bytes32,address)", "0x1dbf3bc7": "spend(uint256)", "0x36b81feb": "Deed(address)", "0xf47289e1": "_ecDouble(uint256,uint256,uint256)", "0x026993e0": "Midas(address,address)", "0x5e404de3": "setMaximumCredit(uint256)", "0x0194db8e": "sum(uint256[])", "0xa04a0908": "execute(address,bytes,uint256)", "0x2b4a3b31": "doTransferFrom(address,address,uint256)", "0x96ed10a4": "issuePOIs()", "0xb75c7dc6": "revoke(bytes32)", "0x6056969b": "announce(bytes32)", "0xd63547e6": "GetFreeCnt()", "0x788e26e7": "sponsorDeposit()", "0x550dd006": "calcCostsBuying(uint256,uint8,uint8,uint256)", "0xd4b1d19f": "testThrowsTransferDisabled()", "0x04706fdf": "giveContributionsBackProfitBugged()", "0x5d5483b3": "WatchAppliedFeePercentage()", "0x6bf8f85a": "forceFinish()", "0x3edd90e7": "NewOwner(address)", "0x7c69b5d1": "NewDeposit(uint256)", "0x866f6736": "trustedChildWithdraw()", "0xcdcb7c8f": "chase()", "0x60dccd89": "getContentAccount(uint256)", "0xbff1f9e1": "totalUsers()", "0x1aca00fd": "variable(uint256)", "0x6e658fbe": "myFundsExpireIn(uint256)", "0xddb1bdc8": "credit(address,uint256,uint256)", "0x934bc29d": "exampleFunction(uint256)", "0x113e6b66": "fipsAddToLedger(bytes20,address)", "0x1a88bc66": "slot()", "0xec97cff7": "addCertificationDocument(address,bytes32)", "0x0790e880": "setBlockappsAddr(address)", "0xe0ad411d": "assets(bytes)", "0x791b51f1": "Consulting(address,address)", "0xa26dbf26": "totalParticipants()", "0xb78b52df": "allocate(address,uint256)", "0xdb29fe12": "addShareholder(address)", "0x06459119": "testThrowsTransferNotTransferable()", "0xbadbaa3c": "setCallData()", "0x2c6b2c92": "checkProfitLossSinceInvestorChange()", "0x8aa6f1b1": "setUltimateOutcome(bytes32)", "0xecb98714": "random_damage(uint256)", "0x506e106c": "setToS(string)", "0xf0d474f9": "underdogCount()", "0x2212dbc3": "get_timestamp()", "0xd504ea1d": "getArray()", "0x9b29cb23": "getDailyPayment()", "0x9d3e069c": "StartDraw()", "0x12494160": "isHolder()", "0xbbd4e8c9": "numDeposits()", "0xfea2920e": "createNewDraw()", "0xff556ecb": "releaseUnicorn(uint256)", "0x3bed33ce": "withdrawEther(uint256)", "0xaa9669c1": "roll(uint256,bytes)", "0xa00aede9": "scheduleCall(uint256,address)", "0xc0819961": "Invest()", "0x1ed24195": "getPeriod()", "0x5babb758": "testSetUp()", "0xaaf9d13e": "buyTopDog(uint256,uint256)", "0x7c45ef6c": "stringToSig(string,string)", "0x7353f62b": "testGetApprovalDb()", "0xef7507c8": "testWinner(uint256)", "0x7ef95c6f": "extractAccountAllowanceRecordLength(address)", "0x66099706": "getChannelCred(address,uint256)", "0x5c242c59": "query1(uint256,string,string,uint256)", "0x299a7bcc": "setOwner(address,address)", "0xe1152343": "payout(uint256)", "0xd40a71fb": "step1()", "0xda9c6a46": "getReplyCount(uint256)", "0xffb7bfba": "watchProposal(uint256)", "0x2e1a7d4d": "withdraw(uint256)", "0x03da8902": "transfearDBOwner(address)", "0xc9bd2893": "fines()", "0xfdd3a879": "quick()", "0xda0774ad": "getCallFeeScalar(uint256,uint256)", "0x0f2c9329": "split(address,address)", "0xa3912ec8": "receiveEther()", "0xfd6f5430": "setContent(string,bytes32)", "0x99e0021f": "mergencyCall()", "0xb7aec6a5": "scheduleCall(address,bytes,uint256,uint256,uint8,uint256)", "0x23145ca0": "forceCheck()", "0xc7cf28fe": "canClaimTimeout()", "0x26db7648": "proposedVersion()", "0x60a60fd8": "testProxyCallWithValue()", "0x044d0b06": "oraclize_query(string,string[2])", "0x4f013184": "investInTheSystem()", "0x0c9fd581": "assertTrue(bool)", "0x09574810": "getOperationsNumber()", "0x6e2edf30": "ETCSurvey(address)", "0x3cc86b80": "GetMoney(uint256,address)", "0xf7b89a3e": "getTotalCosts()", "0xb18c6847": "manualUpdateBalances()", "0x8a65d874": "userStats(address)", "0xf80b3cfa": "checkBetLowhigh(uint8)", "0xc2def3b9": "getOrganizer()", "0x2dae9878": "BankOwner_EnableConnectBankAccountToNewOwnerAddress()", "0x1998aeef": "bid()", "0xc64e8bc0": "executeN(uint256)", "0xd4088e33": "setPrice(uint256,uint256,uint64)", "0xd263b7eb": "ownerkill()", "0xc478fc37": "EtherWheel(uint256,uint256,uint8)", "0x05b765ea": "getCertifierStatus(address)", "0x93c32e06": "changeFounder(address)", "0xf207564e": "register(uint256)", "0xae6c0b03": "canWithdrawBond(uint256)", "0x2b1071c9": "testTransferToNullAuthority()", "0xd9feeeb6": "fillMyOrder(uint256)", "0x9fb755d7": "setHotWallet(address)", "0x7f98444f": "randomEnd()", "0xf3fef3a3": "withdraw(address,uint256)", "0x48a0d754": "available()", "0x3af94817": "getPongvalRemote()", "0xec21a913": "setUint256(int256,uint256)", "0x6099af40": "setConfigBool(bytes,bool)", "0xf0cbe059": "proxyTransferFromWithReference(address,address,uint256,bytes32,string)", "0xf93589ce": "didWin(bytes)", "0x1eb5ea2e": "returnFunds()", "0xa6027d53": "IconomiTokenTest(uint256,string,uint8,string,uint256)", "0x8a323b38": "Contract(uint256,string,uint8,string)", "0xb0f07e44": "registerData()", "0xc9d27afe": "vote(uint256,bool)", "0x64265b1a": "share_transfered(string)", "0x78205f67": "testThrowTransferEnableNotTransferable()", "0x081780f4": "clearRecord(bytes32)", "0xdd137b5d": "toBase58(uint256,uint8)", "0x9483e91a": "withdraw(address,uint256,bytes,uint256)", "0xc6502da8": "basePayment()", "0xe17e1274": "testTransferToRejectAuthority()", "0x9af605cb": "__proxy(address,bytes,uint256)", "0x7a8df1b9": "getAffiliateInfo(address)", "0x46b305d6": "lockBetsForWithdraw()", "0x7d4cf602": "buildDSBalanceDB()", "0xce87f626": "replaceWizardRP(address)", "0x125b8f06": "isInNextGeneration()", "0xd0068f80": "getClient(uint256)", "0x7f0899f2": "AddTicket(bytes5[])", "0xb15dcc25": "query(address,bytes2,uint256)", "0x07a9574a": "changeLeaderMessage(string)", "0x16e55626": "getDogName(address)", "0xbc058968": "updateThingData(bytes32[],bytes32[],uint88)", "0x02aa274b": "setForward(bytes4,address)", "0x08f235ec": "getDefaultPayment()", "0x1dd4914b": "withdrawEtherOrThrow(uint256)", "0x7ca31724": "tokenId(address)", "0x0c4f65bd": "getOwnerAddress()", "0xeec3cb41": "placeBet(bool[],uint256,uint256)", "0x9054bdec": "toTimestamp(uint16,uint8,uint8,uint8,uint8,uint8)", "0x468f02d2": "getUnderlyingPrice()", "0x74331be7": "sete(address)", "0xb05e390a": "TokenEther(string,string)", "0x89cc5ea8": "bid(string,address,uint256)", "0xa8893a6e": "getNumOfSalesWithSameId(bytes16)", "0x3defb962": "heartbeat()", "0x15a03930": "TossMyCoin()", "0x1d8ae626": "Security(string,string)", "0xf1bca7a4": "doCall(uint256)", "0xae6215d8": "getBlockHeight(bytes)", "0x8124bb0f": "continueExecution()", "0xc1cbbca7": "contribute(uint256)", "0xa48566ba": "serverSeed(address,bytes)", "0xc0f5a9cb": "deleteThing(bytes32[])", "0x4136aa35": "isAlive()", "0x6fe665e9": "SlotMachine()", "0xfaff50a8": "rootNode()", "0xaf769eff": "Paper()", "0x77863b61": "CrossWhitehatWithdraw(uint256,address)", "0x2bed55b0": "buildDSEasyMultisig(uint256,uint256,uint256)", "0xd2ef7398": "challenge()", "0x96e4ee3d": "convert(uint256,uint256)", "0x2dff6941": "content(bytes32)", "0x4d536f9f": "validateNameExt(bytes)", "0xd4d5d32a": "collectFee()", "0x6620a935": "sendToOwner()", "0x5084da18": "fipsOwner(bytes20)", "0xe419f189": "multiAccessIsOwner(address)", "0xa9fbc614": "lookupTicketHolder(uint256)", "0x11f72496": "testT()", "0x7365870b": "bet(uint256)", "0x09861b81": "flooredSub(uint256,uint256)", "0xe28fed1e": "userRescues(address)", "0x28cc413a": "getProof(uint256,uint256,uint256)", "0x9a8f09bd": "newKing(address)", "0x5d068051": "sendFees(address)", "0x49cbe338": "tryRead(uint64)", "0x691bfc89": "goods(uint16,uint256)", "0xfc36e15b": "vote(string)", "0x48107843": "getNextCallSibling(address)", "0x6461fe39": "transferFromWithReference(address,address,uint256,string)", "0x804e11dc": "testThrowsDisownNotTransferable()", "0x76f30ca1": "toContentID(address,uint256,string,bytes)", "0xa77b2e37": "Coin()", "0x3f2f1596": "setupTreasury(address,uint256)", "0x3de9e4c6": "__transferFromWithReference(address,address,uint256,string)", "0x612e45a3": "newProposal(address,uint256,string,bytes,uint256,bool)", "0x4cdb48e4": "isValidNym(address)", "0x5afa5036": "isCertified(address)", "0x9a1b420b": "OraclizeAddrResolver()", "0xec5c9036": "Crowdsale(address,uint256,uint256)", "0x01095962": "oraclize_setCustomGasPrice(uint256)", "0xe7e2aa0e": "buyer_cancel()", "0x2da8f764": "submitVideo(string,string)", "0x3395dc70": "acceptTransfer(address,address,uint256)", "0xdf143fb7": "HackerGold(address)", "0x63334c58": "transferETC(address)", "0x6a5da6e5": "followCampaign(uint256)", "0x49fb2dc5": "add_to_association(uint256,uint256,uint256)", "0x953aa435": "GetPrice(uint8)", "0xc1257bad": "testPassingAProposal()", "0x3bc5de30": "getData()", "0x4abb9d39": "depletable()", "0x129484b6": "changeFeeRecipient(int256,int256,int256,int256,int256,int256)", "0x902e64e5": "Oath()", "0x8c4dd5cd": "Democracy()", "0xbea124a6": "query(bytes,bytes,int256)", "0x69347990": "ownerWithdrawl()", "0x606deecd": "requestData()", "0x6720ceb1": "sendPayment()", "0xb1050da5": "newProposal(address,uint256,string,bytes)", "0xfeaa29d8": "insertProfitHere()", "0x36f9f49c": "etherandomSeed()", "0xae815843": "query(uint256,string,string,uint256)", "0x752d349c": "depthCheck(int256,int256)", "0xa24835d1": "destroy(address,uint256)", "0x26070774": "Token(address)", "0x6e0d98fe": "setProbabilities(uint32[])", "0x0761a004": "step(uint256,bytes)", "0xad82dcac": "testBlockhashCorrectFee()", "0x0900f010": "upgrade(address)", "0xa288fb1f": "setConfigUint(int256,bytes,uint256)", "0xe88b8ac6": "confirmAndCheck(bytes)", "0xf1a00a53": "unregisterListening(address)", "0xb17acdcd": "collectFees(uint256)", "0xb5d0f16e": "getGasScalar(uint256,uint256)", "0xae999ece": "reserve(string)", "0x95ceb4b3": "winningProtocal()", "0x1a93fa4b": "reorganizeSubUsers()", "0x9243e088": "setEnforceRevisions(bytes20)", "0x342454c7": "isDigit(bytes1)", "0x8e2c6f4d": "initiateVerification(address,bytes,bytes)", "0xddb5b3ac": "SellTokens()", "0xd18dfdc9": "parrot(uint256)", "0xd3732642": "FastRealisticPyramid()", "0x37ab8f20": "notifyPlayer(uint256,uint256,uint256,uint256)", "0xfb6e155f": "availableVolume(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32)", "0x50ab6f7f": "getMsgs()", "0x6b6a53fa": "testThrowsRestartNotOwner()", "0x4dc7cc55": "terminateAlt()", "0x8b9726c1": "multiAccessCallD(address,uint256,bytes,address)", "0x5a74dee5": "multiAccessRemoveOwnerD(address,address)", "0xfd339d18": "testAuthorityTryAuthUnauthorized()", "0xd3d6a975": "testThrowsTransferNotEnabled()", "0x8aa001fc": "getSecond(uint256)", "0x2ec2c246": "unregister(address)", "0xbbdb31cb": "challenge(uint256,address,bool)", "0x635cfda2": "Incrementer()", "0x704b6c02": "setAdmin(address)", "0xc1c723f4": "validateProposedMonarchName(bytes)", "0xadf5e565": "verify(bytes,address,uint256,uint8,bytes,bytes)", "0xbe7cddf8": "TwoD()", "0xc71b583b": "closeRequest()", "0x9cbf9e36": "createToken()", "0x69c4113d": "setNewBudget(uint256,uint256,uint256,uint256)", "0xd4649fde": "expire(uint256,uint8,bytes32,bytes32,bytes32)", "0xf9a7a2ef": "named(bytes)", "0xdbf45aa3": "EthBank()", "0xbb3ce7fe": "DepositHolder()", "0xfb5d5729": "getPongvalTransactional()", "0x2f0b15f6": "testGetUnset()", "0x62891b5d": "multiAccessChangeRequirement(uint256)", "0x538e0759": "refill()", "0x24c65f35": "updateRefundGas()", "0x62b24189": "DepositToBankAccountFromDifferentAddress(uint32)", "0xd81f53fd": "EtherId()", "0xcea943ee": "getSaleConfig()", "0x23647398": "testThrowRetractNotOwner()", "0xcdcd77c0": "baz(uint32,bool)", "0xa677fbd9": "example2Func()", "0xa02b9aac": "getPaymentDataByAddress(address)", "0x268eb055": "setDescription(uint64,bytes)", "0x09d2d0b9": "setServiceAccount(address,bool)", "0x5f70d9ac": "getBot(uint256)", "0x63f80de3": "issueCoin(address,uint256,uint256)", "0x8570153e": "publish(string,string,bytes,address[])", "0x7975c56e": "oraclize_query(uint256,string,string)", "0x7c3064f1": "refundStake()", "0xef4ffee2": "Honestgamble()", "0xfdc193a4": "test3Fails()", "0x0eb495c2": "pushCity()", "0x6a357465": "payHours(address,uint256)", "0x93f0bb51": "order(address,uint256,address,uint256,uint256,uint256,uint8,bytes32,bytes32)", "0x0a9254e4": "setUp()", "0xc490a266": "toUInt(bytes)", "0xf666323e": "UUIDProvider()", "0x857d4c07": "throwScraps(uint256)", "0x7fd8ee68": "computeNameHashExt(bytes)", "0x7bd703e8": "getBalanceInEth(address)", "0x68f65f02": "ChangeShownDenomination(bool,bool,bool,bool)", "0xc7f758a8": "getProposal(uint256)", "0x824dbc9a": "changeMembership(address,uint256,bool,string)", "0x2a228fc2": "processWithdrawals()", "0xe0457884": "betResolution(uint8,uint8,uint8,bool)", "0x550538f6": "getOneTimeCosts()", "0xf3e84cf3": "createNewRevision(bytes32,bytes)", "0x77fe38a4": "transferToICAPWithReference(bytes32,uint256,string)", "0xcc189d00": "Vault(address,uint256)", "0x9e281a98": "withdrawToken(address,uint256)", "0x7b12df39": "userProfits()", "0xa843c97f": "attack(uint256,uint256,uint256[])", "0x9da680f3": "adjustRegistrationFee(uint256)", "0x3ea3f6c5": "activateRegistrar()", "0xb36df681": "ExecutableBase()", "0x0f3a1412": "getArrlist(uint256,uint256)", "0x8b7f0ddd": "register(address,address,string,string,bytes32[],uint256,string)", "0xb5d3a379": "CanaryTestnet()", "0x9894221a": "SendCashForHardwareReturn()", "0xc2b12a73": "setBytes32(bytes32)", "0xff1b4341": "easyPropose(address,uint256,uint256)", "0x927ed13a": "newClient(uint256,address)", "0x9b9d0364": "_setFeeStructure(uint256,uint256,uint256)", "0x01518d76": "sendQuery(uint256)", "0x4112b7f1": "tryGetNameOwner(bytes)", "0xb759f954": "approve(uint256)", "0x810a882f": "setConfigBytes(bytes32,bytes32)", "0xea7a7184": "testGetBalanceDb()", "0xb0c8f9dc": "add(string)", "0xe59f611f": "InputLimit(uint256)", "0xdce293a7": "minLength(uint256)", "0xf509b627": "confirm(address,uint224,uint32,address)", "0xd48bfca7": "addToken(address)", "0x044f9ac8": "findThroneCalled(bytes)", "0x1d57bcf7": "ProofLibInterface()", "0x75830463": "checkBetLowhigh(uint8,address,bytes32,bytes32)", "0x2a45a39a": "Post(address)", "0x29cbdc86": "buyin(address,uint256)", "0x3cbfed74": "getBondBalance()", "0x80a23ddf": "mintBadge(int256,address,uint256)", "0x96b76c23": "stand(uint256)", "0xc392f5a0": "getAllPackageReleaseHashes(string)", "0x4847a79c": "_transfer(address,uint256)", "0x905e6e42": "JSON_Test()", "0x9f489e4e": "getDeposit(uint256,address)", "0x4f8e624e": "Greeter(string)", "0x96013c9c": "testLatestPkgGetter()", "0xe4fc6b6d": "distribute()", "0x423d4ef2": "createChannel()", "0x24d7806c": "isAdmin(address)", "0x691fb8ea": "jumpIn()", "0xd50f6bf0": "transferETH(address)", "0xd0e0813a": "promote(address)", "0x528eedcb": "sendSafe(address,address,uint256)", "0x00faf4dd": "getTokenDivisor()", "0x46a1d95f": "closeMarket(bytes)", "0x318a3fee": "relayTx(bytes,int256,int256[],int256,int256)", "0x49d55d9d": "receiveTransfer(uint256)", "0x4fa99dd0": "Matching_Ethers()", "0x99a5d747": "calculateFee(uint256)", "0x3c67c51e": "testLogs()", "0x12ab7242": "setupStackDepthLib(address)", "0xad9ec17e": "setGreyToken()", "0xc37e8cb2": "testExportAuthorized()", "0x43046844": "placeBet(uint8)", "0xc6e1c178": "TheLuckyOne(bytes)", "0x13d1aa2e": "f(uint256,uint256)", "0x64a4a5d7": "testBitsEqualSuccess()", "0xfb1669ca": "setBalance(uint256)", "0x40fdef80": "administration(uint256,string,uint256,uint256,address)", "0xcf7315c6": "retract(bytes20)", "0x76196c88": "setDnsrr(bytes32,bytes)", "0x08bf2d0d": "getOrderBook(uint256,uint256)", "0x021c309a": "solveBet(address,uint8,bool,uint8)", "0x4de162e4": "extractAccountLength()", "0x56fa47f0": "split(address)", "0xb3a0b1ef": "basicInfoGetter()", "0x26066ad5": "offer(uint256,bytes,uint256,bytes)", "0x99c724ef": "skipInLine(uint256,uint256)", "0x838445e8": "EtherAds(address,address,address)", "0xe06174e4": "settings()", "0xfac5bb92": "getPreRelease(bytes32)", "0x93c94acb": "calculateRewards(uint256[3][3])", "0xd7fa1007": "setHash(bytes32,bytes32)", "0x2a714078": "triggerAuth()", "0x4cd995da": "registerCompany(address,string)", "0xf6469342": "_setPackedBlockNumber(bytes32,uint256)", "0x8e7fd292": "trySetSubnodeOwner(bytes32,address)", "0x4f573cb2": "withdrawRevenue()", "0x924c28c1": "ContractInterface(address,address,address)", "0x4fc9c91a": "identityOf(bytes32)", "0x19901f1d": "TokenSale(uint256,uint256)", "0xaf8b7525": "CollectAndReduceFees(uint256)", "0x3ccb7dc9": "CrowdFund(uint256,uint256)", "0xaeeb96af": "Highlander()", "0xa126c5df": "GAS_TO_AUTHORIZE_EXECUTION()", "0x13c89a8f": "getAllowedTime(bytes32)", "0xf38b0600": "fireEventLog3()", "0xc7144269": "changeSettings_only_Dev(uint256,uint256,uint256,uint256,uint16,uint256,uint256,uint256,uint8,uint8)", "0xefc81a8c": "create()", "0x7429c086": "repeat()", "0x9c0a4bbc": "AlwaysFail()", "0xc3d23e10": "checkBet()", "0x28a45038": "testTryProxyCall()", "0xa668d7c9": "NiceGuyPonzi()", "0x06fe1fd7": "getPackageName(bytes32)", "0x29f27577": "InvestorList(uint256)", "0x57e25a79": "PullPaymentCapable()", "0x6d1669e1": "approveAndCall(address,address,uint256,bytes)", "0xa7e93e87": "retractLatestRevision(bytes20)", "0x9c7e8a03": "addParticipant(address,address,uint256)", "0xc6ab4514": "sendRobust(address,uint256,uint256)", "0xe8f6bc2e": "changeAccountLevelsAddr(address)", "0xb5d1990d": "numRecords()", "0x3e853128": "getGasForXau(address)", "0xa1add510": "hasRelation(bytes32,bytes32,address)", "0x31e3e2fe": "WithDraw()", "0x86723215": "createMarket(bytes,uint256,uint256,address)", "0xce845d1d": "currentBalance()", "0xc3a2c0c3": "scheduleCall()", "0xcf8eeb7e": "subBalance(address,uint256)", "0xaeb4f0d3": "RegisterTwo(address,address)", "0x3c716e08": "updateAuthority(address)", "0x9919b1cc": "getContentsByRanks(address,uint256,uint256,uint256)", "0x0f06670a": "didWin(bytes32)", "0x74e4435f": "getUserAddress(uint256,bytes32)", "0x4664b235": "bytes32_to_bytes(bytes,bytes,bytes)", "0x2ac9bf09": "bid(uint256,uint256,uint256)", "0xf11c4482": "approveFromProxy(address,address,uint256)", "0xfe992c98": "balanceOfAll(address)", "0x43e332c5": "Last_block_number_and_blockhash_used()", "0x0066753e": "removeCertifier(address)", "0xd299dac0": "blake2b(bytes,bytes,uint64)", "0x41395efa": "dgxBalance()", "0xac1b14ff": "proxyCall(uint256)", "0x7a6ce2e1": "getMsgSender()", "0x3855dcd6": "getContrarians_by_index(uint256)", "0xe6febc9b": "investorWithdraw(uint256)", "0xe6e91cfc": "voidFailedPayment(uint256)", "0x547eeac1": "acceptTransfer()", "0x9824425a": "takeOrder(uint256,uint256,uint256,uint256)", "0xdf25ee23": "getIndexId(address,bytes)", "0x0f3d7c3e": "release(string,uint32[3],string,string,string)", "0x15cff546": "isOperationBlocked()", "0x0b927666": "order(address,uint256,address,uint256,uint256,uint256)", "0x00ce2057": "triggerPayment()", "0x9a9c29f6": "settle(uint256,uint256)", "0x0f096163": "Chainy()", "0x2f5a5c5b": "timegame()", "0x900d85fa": "updatePreReleaseTree(bytes32)", "0x1cbd0519": "accountLevel(address)", "0x29a065bd": "getLOg(uint256)", "0xcec95aa1": "getReleaseHashForPackage(string,uint256)", "0x41524433": "sellKissBTCWithCallback(uint256,address,uint256)", "0x5e431709": "sealedBids(address,bytes32)", "0xf55b23c0": "externalLeave()", "0x31375242": "ownerSetTreasury(address)", "0x51b42b00": "deactivate()", "0x5af36e3e": "refund(uint256,uint256)", "0xc5096a69": "feeFor(address,address,uint256)", "0x059a500c": "makeDeposit(uint256)", "0x3c2e7d54": "priv_inMainChain__(int256,int256)", "0x9431f5f0": "withdrawFees(bytes)", "0x91b4a0e7": "Difficulty()", "0x268d50fe": "ownerSetHouseEdge(uint256)", "0x9644fcbd": "changeMembership(address,bool,string)", "0x66aa6f26": "payFee(bytes)", "0x353928d8": "helpRed()", "0x9c1193ea": "GreeterA(bytes)", "0xdd79e33e": "splitIdentifiers(string)", "0x4d268ddd": "payImporterBankForGoodsBought()", "0x656d2f63": "ManagedAccount(address)", "0x1216e771": "expiration(uint64)", "0x36f7cd70": "setPricePerStake(uint256)", "0x7842a3a4": "payReward()", "0x0ae50a39": "GetOwner()", "0xc81caae7": "acceptMember(address,string,string)", "0xe50dce71": "testControllerApproveSetsAllowance()", "0x4b64e492": "execute(address)", "0xd9e947f3": "kickOutMember(address)", "0x35cc59a9": "createSchema(bytes)", "0x2530c905": "rand(uint256)", "0x4894e37f": "__callback(bytes,string,bytes)", "0x70480275": "addAdmin(address)", "0x969cb7c3": "getPublisher(uint256)", "0x4ed4831a": "all(bool[7])", "0x2ef3accc": "getPrice(string,uint256)", "0x67854643": "getGenerationMemberLength(uint256)", "0xe6690fb1": "nextAuction(uint256)", "0x5829d310": "entries(int256)", "0x7fe1dc7e": "getToken(bytes)", "0xe7329e71": "scheduleCall(bytes,bytes,uint256,uint256,uint8,uint256)", "0x41c12a70": "voteNo()", "0x6a28db13": "getQrLength()", "0xdd93890b": "setMeta(uint256,bytes32,bytes32)", "0xa48bdb7c": "results()", "0x9d888e86": "currentVersion()", "0xff81fb91": "unhint(int256,bytes32)", "0x9ec32d45": "challengeWinningOutcome(bytes,uint16)", "0xa0a2f629": "setReferralId(uint256,address)", "0x76577eae": "distributeEarnings()", "0x3e5cee05": "issueIOU(string,uint256,address)", "0xf3c7d275": "prenup(string,string,string,string,string,address,address)", "0x7154ae61": "CheckNumbers(uint8[5])", "0x05de4f07": "getContentParent(uint256)", "0xb81e43fc": "getEventName()", "0xa7eeea37": "NewContributor(uint256)", "0xe816a515": "takeFlight()", "0x05b2b03a": "CertificationCentre(address)", "0x74d4ab27": "fipsRegister()", "0x65fa2f7f": "getLastPrice(uint256)", "0xcc8b34ab": "CrowdCoin()", "0xe2b178a0": "getAuthority()", "0x5fb64fd6": "checkMembership(address)", "0x7948f523": "setAmbiAddress(address,bytes32)", "0xebb71194": "withdrawFees(bytes32)", "0x6545bed3": "Dice(uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x7065cb48": "addOwner(address)", "0x913f424c": "_ecMul(uint256,uint256,uint256,uint256)", "0xdbecc372": "Example(uint256)", "0x9f7f760c": "SimpleDice()", "0xca94692d": "abiSignature()", "0x61ba3377": "WatchLastTime()", "0x20e647e1": "checkBetColor(uint8,address,bytes32,bytes32)", "0x0a3b0a4f": "add(address)", "0xc51cf179": "calcBaseFeeForShares(uint256)", "0x8baced64": "isInPool(address)", "0x4dc415de": "reject()", "0x1555e337": "ConferenceCertificate()", "0x9555a942": "withdrawFrom(address,address,uint256)", "0xe1efda6d": "airaSend(address,address,uint256)", "0x8bfc2f33": "delegateDAOTokens(uint256)", "0xb964608d": "get_return_by_level(uint256)", "0x0c1fad51": "setSeedSourceA(address)", "0x98688a95": "Ai()", "0xd930a90b": "testFailMoveBalanceDueToInsufficientFunds()", "0x337b5988": "testSimpleNameRegister()", "0xf06d335e": "_recoverAccount(address,address)", "0x025bbbe5": "newSale(bytes16,uint256,uint256)", "0x984413b8": "_eraseNode(bytes32)", "0x5ea187c9": "BuildByteArray(bytes)", "0xc2a95cc9": "updateTrustSettings(address,uint256)", "0xde0ff7c5": "getEther()", "0x4ac7becf": "SimpleSign()", "0x252786e4": "WatchBlockSizeInEther()", "0xf2016a4f": "updateMinEthPerNotification(uint256)", "0xd743ca38": "newWinner(uint256,address,uint256,uint256,uint256)", "0x9eab5253": "getMembers()", "0x51d38d5f": "addDeveloper(address,string)", "0x930ed251": "getSavedVar()", "0x715ef4ff": "resendFailedPayment(uint256)", "0xb1418cf4": "payHouse()", "0xe1569f6b": "testThrowsSetNotRetractableNotOwner()", "0x4ae9af61": "getBotStats(uint256,uint256)", "0xbf8ecf9c": "authProposals()", "0xc00ca383": "getByOwner(address,uint256)", "0xc8e7ca2e": "getMsgData()", "0x711953ef": "setGameAddress(address)", "0x63a8dac2": "changeSettings(uint256,uint256,uint256,uint8,uint256,uint256,uint8,uint8)", "0x72c87075": "testBlockHeaderFetch()", "0x4c7a2254": "checkMyWithdraw()", "0xab91c7b0": "queueLength()", "0x25209260": "PrepareRoll(uint256)", "0x58150c8b": "GameRegistry()", "0x75608264": "get_hash(uint8,bytes32)", "0x6510ef4d": "oraclize_query(uint256,string,string[5])", "0xd57a12f5": "testCheckSigs()", "0x3f415772": "releaseExists(bytes32)", "0xda25c0cd": "ThisExternalAssembly()", "0xf239e528": "sendOneEtherHome()", "0xc4321adb": "investInTheSystem(uint256)", "0x4fab2ca4": "testGetFrontend()", "0x05261aea": "finalize(uint256)", "0x576eac66": "setFundingGoal(uint256)", "0xe75528cc": "buyBuilding(uint256,uint256)", "0x6ed43eb0": "getInvestorList(uint256)", "0xb38415f3": "getConfigBytes(bytes)", "0x771ad635": "getContentCred(address,uint256)", "0x93c166ec": "computeEndowment(uint256,uint256,uint256,uint256)", "0xac35caee": "transferWithReference(address,uint256,string)", "0xc6803622": "wasCalled()", "0x8cfd8901": "_incBlock()", "0xfcf0f55b": "eventOracles(bytes32,uint256)", "0x505ff574": "register(address,uint256,bool)", "0xf824384a": "addInvestorAtID(uint256)", "0x6b9f96ea": "flush()", "0xc3d0a564": "getAccountBalance(bytes)", "0x30fd300f": "registerBytes32(address,bytes32)", "0xc3169ef2": "respond(uint256,uint256[4])", "0xcf1cd249": "secureSend(address)", "0x62c335c1": "checkCallback(address,uint256,bytes,bytes)", "0xb599afc8": "totalBetCount()", "0x69433e12": "setExchange(uint256)", "0x899942b8": "Devcon2Token()", "0x4c2d71b3": "setConfigAddress(bytes32,address)", "0xb974b0a3": "allData()", "0x27e8c2d8": "burnUnicornShares()", "0xf639365d": "testSetGet()", "0x2f5d3916": "testControllerApproveTriggersEvent()", "0x938b5f32": "origin()", "0xd60dcb5d": "Switch()", "0xde629235": "getCertificationDocumentAtIndex(address,uint256)", "0x329ce29e": "buyTile(uint256)", "0x59e2d30e": "testThrowBlobStoreNotRegistered()", "0xa005b87b": "NullMapTest()", "0xc13afa91": "object_locations(uint256)", "0x4848b1a5": "setData(uint256,uint256)", "0x80ede329": "getDocumentDetails(uint256)", "0x35d13969": "SendAllMoney()", "0x8040cac4": "testOverflow()", "0x9507d39a": "get(uint256)", "0xc040e6b8": "stage()", "0x18178358": "poke()", "0xfd782de5": "Proxy()", "0xfd68a422": "returnmoneycreator(uint8,uint128)", "0x86a50535": "voteFor(uint256)", "0x44602a7d": "testFallbackReturn()", "0xa230c524": "isMember(address)", "0x3ffbd47f": "register(string,string)", "0x8cecf66e": "_inverse(uint256)", "0x51017702": "isOutcomeSet(bytes32)", "0xd408746a": "GetContractAddr()", "0x20130753": "testThrowSetNotRetractableNotOwner()", "0xa0e2abf7": "getFirstActiveGamble()", "0x7c582304": "updateInvestmentTotal(address,uint256)", "0x95d89b41": "symbol()", "0x1768b436": "ETCSurvey()", "0x6d4ce63c": "get()", "0xc41a360a": "getOwner(uint256)", "0x49942ccb": "scheduleCall(bytes,bytes,uint256,uint256)", "0x6b64c769": "startAuction()", "0x084d72f4": "getWinningOutcome(uint256)", "0xd379be23": "claimer()", "0x41fa4876": "multiBlockRandomGen(uint256,uint256)", "0x5bc7e259": "updateRelease(uint32,uint32,uint32,bytes,bool)", "0x47f3d794": "configure(uint256,uint8,uint256,uint256,uint256,uint256)", "0xe2bbb158": "deposit(uint256,uint256)", "0x953a7fab": "testMoveBalance()", "0xeacc5b3b": "safeSend(address,uint256,uint256)", "0x96f0aa8f": "findNextSecond(uint256,bytes)", "0xc8690233": "pubkey(bytes32)", "0x459f93f7": "getBuyers(uint256,address)", "0xf714de9c": "MultiAccess()", "0xf4a81d08": "getKudosGiven(address)", "0x5aa94a68": "computeResultVoteExtraInvestFeesRate()", "0xdb2a0cb7": "HumanStandardTokenFactory()", "0xdf3a6b10": "testMemberAddedEvent()", "0xce8d054e": "_setupNoCallback()", "0x8ea822d8": "createThings(bytes32[],uint16[],bytes32[],uint16[],uint88)", "0x24fb563f": "PlayerTickets(address,uint256,uint256)", "0x8c0e156d": "scheduleCall(bytes4,uint256,uint256)", "0xef04fdb7": "buyShares(bytes,uint8,uint256,uint256)", "0xa0afd731": "dividendBalance(address)", "0xc3c95c7b": "getMarket(bytes32)", "0x94ed9b77": "append(address,address)", "0xc87b36ed": "disableBetting()", "0x566735d8": "PreVNK(uint256,string,string,uint8)", "0x400aae08": "isInCurrentGeneration(address)", "0x44dd4b5e": "scheduleTransaction(address,uint256,bytes)", "0x48c54b9d": "claimTokens()", "0xe8930efd": "Investors(address)", "0xa6f2ae3a": "buy()", "0x12819817": "setXauForGasCurrator(address)", "0x056e1059": "oraclize_query(uint256,string,string,uint256)", "0x7824407f": "tokenSupply()", "0x7f0c949c": "setJurisdication(string)", "0x2e817963": "set_sdl(address)", "0xaee84f6b": "setTime(address,uint256)", "0x3c0dde1c": "_addPools(address,address)", "0xf8bd526e": "setCoinageContract(address)", "0x04b07a5e": "removeUpdater(address)", "0x11149ada": "getProof(uint256)", "0x4306cc3f": "queryEarnings(address)", "0x55241077": "setValue(uint256)", "0x492b67ea": "Etherdoc()", "0xadf59f99": "query(uint256,string,string)", "0x951b01c5": "setCertifierDb(address)", "0x8ae986cf": "registrantApprove(address)", "0xfa68b4ce": "lookupISO3116_1_alpha_3(bytes)", "0x7fdc8290": "isUnderscore(bytes1)", "0x89495172": "convictFinal(uint256,uint256)", "0x93e02d13": "FallenLeaders()", "0x3e476053": "moveFunds(address,uint256)", "0x8894dd2b": "addEther()", "0x8f03850b": "numContributors()", "0xbfc3cd2f": "testFailChargeMoreThanApproved()", "0x1d82e9c7": "EXTRA_GAS()", "0x278ecde1": "refund(uint256)", "0x0f825673": "deleteCoupon(string)", "0xa324ad24": "getMonth(uint256)", "0xd628e0a6": "WatchBalance()", "0xb238ad0e": "getDaysInMonth(uint8,uint16)", "0xd6febde8": "buy(uint256,uint256)", "0x370ec1c5": "_fillOrder(address,uint256)", "0x4c33fe94": "cancel(address)", "0xcdd13701": "getEventHashes(uint256[256])", "0xe1bc3003": "reveal(bytes,string)", "0xa2e62045": "update()", "0x75f45878": "scheduleCall(bytes,bytes,uint256)", "0xd2756e11": "finalizeNumber(uint256)", "0x48519189": "MonedaAlcala(string,string)", "0x009b9369": "getVoteNumber(uint256)", "0xdaa283c8": "__callback(bytes,string)", "0xfcce2622": "challengeAnswer(uint256,bytes)", "0xac18de43": "removeManager(address)", "0x16d9356f": "oraclize_query(string,string[4])", "0xd1734eac": "isInNextGeneration(address)", "0x524fa7b9": "whitelistAdd(address)", "0xa5eb7a4e": "operated()", "0xb0aab296": "getNextNode(bytes)", "0x1982ed58": "ChangeReuseCashInHarware(bool,uint16,uint16)", "0xdf811d7d": "numberOfPlayersInCurrentRound()", "0xca7dc5b1": "getNumberOfTweets()", "0x488b3538": "shares(address,bytes32,int256)", "0xebd83378": "get_blocks_for(uint256)", "0x399fdb86": "testFailNormalWhitelistReset()", "0xca0c1e62": "computeMerkle(int256,int256,int256[],int256,int256,int256[])", "0x8963dab4": "getNodeId(bytes,bytes)", "0x7d94792a": "seed()", "0xbcf175c8": "oraclize_cbAddress()", "0x38eee93e": "scheduleCall(address,bytes,bytes,uint16,uint8,uint256[5])", "0xa08d3f83": "Etheropt(uint256,string,uint256,uint256,bytes32,address,int256[])", "0xae47a290": "changeMaxBet(uint256)", "0xd12c1e28": "badgesOf(address)", "0x001f8d11": "removePackage(bytes32,string)", "0x54fd4d50": "version()", "0x89abeb19": "ProcessGameExt(uint256)", "0x3dd7c1b9": "newProduct(string,string,uint256,uint256)", "0xa396541e": "getPongvalTxRetrievalAttempted()", "0xcc8af0fe": "bytesToUInt(bytes,bytes)", "0x983b94fb": "finalizeAuction(bytes32)", "0x3df91162": "getUpdatable(bytes20)", "0x045236b4": "getChainyData(string)", "0x9c172f87": "EthVentures4()", "0x996a4be3": "uintToBytes(uint256,uint256)", "0x775a8f5e": "toBytes(uint256)", "0xb6db75a0": "isAdmin()", "0x0b6fcdb0": "getEnforceRevisions(bytes32)", "0x29d6f899": "BetOnBlue()", "0xe6cbcba9": "PlusOnePonzi()", "0xc9030ea0": "addMember(address,bool)", "0x8f283970": "changeAdmin(address)", "0x670c884e": "setup(address,uint256,uint256,uint256,address)", "0x808ab1d6": "getCertificationDbCount()", "0x018f5472": "isAUser(address)", "0x59c87d70": "request(bytes32)", "0x407cfe5e": "get_all_players()", "0x33f472b9": "MPO()", "0x662dbe96": "getNodeHeight(bytes)", "0x60b1e173": "getProof(uint256,address,address)", "0xf25eb5c1": "removeReverse()", "0x1d065dde": "_transferWithReward(address,address,uint256)", "0x65343fcb": "TrustEth()", "0xaa237e21": "set(bool,uint256)", "0x60e519c0": "computeMarginAmount()", "0xd9597016": "multisetCustomGasPrice(uint256[],address[])", "0x4f10acc1": "updateGoldFeeData(uint256)", "0x1e9ea66a": "balanceEther10000000(uint256)", "0xffe34512": "getNumChannels(address)", "0x71dd8862": "IndexOf()", "0xdd9dd688": "calcStopPrice()", "0x934354e7": "finishSpin()", "0x26881518": "setupFee(address)", "0x5eb3f639": "assertTrue(bool,bytes)", "0xe9540395": "getRewardDivisor()", "0x8e4afa51": "checkTransferToICAP(bytes32,uint256)", "0xb5b33eda": "scheduleCall(address,uint256)", "0x3d6a32bd": "createTradeContract(address,uint256,uint256,uint256,bool,bool)", "0x5fd9dff6": "allowance(address,address,bytes)", "0x0d244d68": "setNotRetractable(bytes32)", "0xe63697c8": "withdraw(uint256,address,uint256)", "0x3e5087cc": "testBasicThing()", "0xee77fe86": "scheduleCall(address,bytes4,bytes,uint256,uint256,uint8)", "0xb1cc4348": "placeWager()", "0xb95594e5": "lineOfPlayers(uint256)", "0xa9cc4718": "fail()", "0x54385526": "setStatus(uint8,uint8,string)", "0xb45c48dc": "Security_AddPasswordSha3HashToBankAccount(bytes)", "0xace51abc": "helperVerifyHash__(uint256,int256,int256[],int256,uint256,int256,int256[],int256)", "0x1f5d0b4c": "address(address,address,uint256)", "0x7acbfb65": "setOwner(uint256,uint256)", "0x3462f32d": "execWithGasLimit(bytes32,bytes32,uint256,uint256)", "0xac04f5a7": "append(address)", "0x2fcb6628": "_stringGas(string,string)", "0xe977992d": "Doubler()", "0xc57a050e": "fairandeasy()", "0x412664ae": "sendToken(address,uint256)", "0x0afa9fb9": "contains(int256,address)", "0xb69ef8a8": "balance()", "0x264c8e9a": "whatWasTheVal()", "0x255016c8": "checkIfExploded()", "0xd716222c": "is_owner(uint256,address)", "0xc398f030": "expire(uint256,uint8,bytes,bytes,bytes)", "0xb7d454a4": "setNotTransferable(bytes32)", "0x4789aaef": "EthereumDice()", "0xc0171112": "timestamp(uint64)", "0x4f60f334": "multiAccessAddOwner(address)", "0x80aed05f": "LooneyDice()", "0x55ba343f": "getMarket(bytes)", "0x943b0747": "RewardOffer(address,address,bytes,uint256,uint256,uint128,uint256)", "0xa27c672a": "owner_reveal_and_commit(uint8,bytes32,bytes32)", "0x8f731077": "extractAllowanceRecordLength(address)", "0xc3d345c4": "getHangoutAddress()", "0xa8978434": "softResolveAnswer(uint256)", "0x7cef6047": "getNavHistory(uint256)", "0xfae14192": "changeFeePercentage(uint256)", "0x2ddbc04a": "play2(address,uint256)", "0x3fb27b85": "seal()", "0xe8038e25": "TokenSale(uint256,uint256,address)", "0x8d92fdf3": "withdrawAsset(uint256)", "0x8579cbde": "getPrice(string,uint256,address)", "0x0d61b519": "executeProposal(uint256)", "0x63a599a4": "emergencyStop()", "0x661e3605": "ConstructorContract(uint256)", "0xfd7c460d": "ciberLottery()", "0x1f83f440": "getPaymentByAddress(address)", "0xdcf73856": "generateGroups()", "0x3c6e03d7": "thewhalegame()", "0x271cd760": "getPackageDb()", "0x53fefd7d": "changeMaxDeposit(uint256)", "0xae169a50": "claimReward(uint256)", "0x6da84ec0": "calcMarketFee(bytes32,uint256)", "0xb16562fe": "fipsRegister(address,bytes)", "0x041fe13d": "onEtherandomSeed(bytes32,bytes32)", "0x4a617faa": "shaBid(bytes32,uint256,bytes32)", "0x052b2aa7": "getRegistrants()", "0x0ff0a4df": "reFund()", "0xe56b9dce": "GetPrize(uint256)", "0x8eec99c8": "setNewAdmin(address)", "0xffcce369": "changeIPFSHash(string)", "0x40fdf515": "issuetender(address,uint256,uint256)", "0xef4bdfdd": "Set_your_game_number_between_1_15(string)", "0xa991cb0e": "respond(uint256)", "0x617fba04": "getRecord(address)", "0x475a9fa9": "issueTokens(address,uint256)", "0xd30fbd0d": "safeSubtract(uint256,uint256)", "0xe54d4051": "receiveInteger(bytes,uint256,uint16)", "0x8023ffbd": "getOverallSize()", "0x8390b02a": "rfindPtr(uint256,uint256,uint256,uint256)", "0x11e99c22": "arrival()", "0x10c1952f": "setLocked()", "0x039a21b8": "tryExecute(address,bytes,uint256)", "0x201dcd7a": "newChallenge(uint256,uint256)", "0x5aebfd14": "createFile(bytes)", "0xa6b1caa3": "gasScalar(uint256)", "0x200538c6": "DTE()", "0xd1738b72": "wroomWroom()", "0x22f607f6": "Escrow()", "0x7e81b6aa": "KingdomFactory()", "0x901d7775": "voteOutMasterKey(address)", "0xd6af9411": "Rouleth()", "0x7b0383b2": "initializeDispute(uint256)", "0x70c9edb7": "BTCRelayTools(address)", "0xe9c31315": "checkBetParity(uint8,address,bytes32,bytes32)", "0xee82ac5e": "getBlockHash(uint256)", "0x727089f1": "extractAllowanceLength()", "0x8bbb5af7": "test1Fails()", "0x47872b42": "unsealBid(bytes32,uint256,bytes32)", "0x46b04e53": "PlayerInfoPerZone(uint256,uint256)", "0x752bacce": "getExecPrice()", "0x89d8ca67": "drawPot(bytes32,bytes32)", "0xc23697a8": "check(address)", "0x0af658ca": "personUpdateActivity(uint256,bool)", "0x24d4e90a": "ln(uint256)", "0x09d33f1d": "addRequest(address,uint256)", "0xc913b552": "getVersions(bytes)", "0xbb3b8dca": "getCertificateHash(bytes)", "0x3809c0bf": "doInfinite()", "0x853552d7": "_slotAddNew(address)", "0xccf1ab9b": "usurpation()", "0xe7dafdb6": "transfer_token(address,address,uint256)", "0x0c77a697": "claimFounders()", "0xda82a035": "sweepCommission()"} \ No newline at end of file diff --git a/cmd/clef/README.md b/cmd/clef/README.md new file mode 100644 index 0000000000..027c22c98f --- /dev/null +++ b/cmd/clef/README.md @@ -0,0 +1,877 @@ +Clef +---- +Clef can be used to sign transactions and data and is meant as a replacement for geth's account management. +This allows DApps not to depend on geth's account management. When a DApp wants to sign data it can send the data to +the signer, the signer will then provide the user with context and asks the user for permission to sign the data. If +the users grants the signing request the signer will send the signature back to the DApp. + +This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can +help in situations when a DApp is connected to a remote node because a local Ethereum node is not available, not +synchronised with the chain or a particular Ethereum node that has no built-in (or limited) account management. + +Clef can run as a daemon on the same machine, or off a usb-stick like [usb armory](https://inversepath.com/usbarmory), +or a separate VM in a [QubesOS](https://www.qubes-os.org/) type os setup. + +Check out + +* the [tutorial](tutorial.md) for some concrete examples on how the signer works. +* the [setup docs](docs/setup.md) for some information on how to configure it to work on QubesOS or USBArmory. + + +## Command line flags +Clef accepts the following command line options: +``` +COMMANDS: + init Initialize the signer, generate secret storage + attest Attest that a js-file is to be used + addpw Store a credential for a keystore file + help Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --loglevel value log level to emit to the screen (default: 4) + --keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore") + --configdir value Directory for clef configuration (default: "$HOME/.clef") + --networkid value Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby) (default: 1) + --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength + --nousb Disables monitoring for and managing USB hardware wallets + --rpcaddr value HTTP-RPC server listening interface (default: "localhost") + --rpcport value HTTP-RPC server listening port (default: 8550) + --signersecret value A file containing the password used to encrypt signer credentials, e.g. keystore credentials and ruleset hash + --4bytedb value File containing 4byte-identifiers (default: "./4byte.json") + --4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json") + --auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log") + --rules value Enable rule-engine (default: "rules.json") + --stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when the signer is started by an external process. + --stdio-ui-test Mechanism to test interface between signer and UI. Requires 'stdio-ui'. + --help, -h show help + --version, -v print the version + +``` + + +Example: +``` +signer -keystore /my/keystore -chainid 4 +``` + + +## Security model + +The security model of the signer is as follows: + +* One critical component (the signer binary / daemon) is responsible for handling cryptographic operations: signing, private keys, encryption/decryption of keystore files. +* The signer binary has a well-defined 'external' API. +* The 'external' API is considered UNTRUSTED. +* The signer binary also communicates with whatever process that invoked the binary, via stdin/stdout. + * This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated. + +The general flow for signing a transaction using e.g. geth is as follows: +![image](sign_flow.png) + +In this case, `geth` would be started with `--externalsigner=http://localhost:8550` and would relay requests to `eth.sendTransaction`. + +## TODOs + +Some snags and todos + +* [ ] The signer should take a startup param "--no-change", for UIs that do not contain the capability + to perform changes to things, only approve/deny. Such a UI should be able to start the signer in + a more secure mode by telling it that it only wants approve/deny capabilities. + +* [x] It would be nice if the signer could collect new 4byte-id:s/method selectors, and have a +secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for +inclusion upstream. + +* It should be possible to configure the signer to check if an account is indeed known to it, before +passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate +accounts if it immediately returned "unknown account". +* [x] It should be possible to configure the signer to auto-allow listing (certain) accounts, instead of asking every time. +* [x] Done Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode), +invoking methods with the following info: + * [x] Version info about the signer + * [x] Address of API (http/ipc) + * [ ] List of known accounts +* [ ] Have a default timeout on signing operations, so that if the user has not answered withing e.g. 60 seconds, the request is rejected. +* [ ] `account_signRawTransaction` +* [ ] `account_bulkSignTransactions([] transactions)` should + * only exist if enabled via config/flag + * only allow non-data-sending transactions + * all txs must use the same `from`-account + * let the user confirm, showing + * the total amount + * the number of unique recipients + +* Geth todos + - The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is +put together is a bit of a hack into the http server. This could probably be greatly improved + - Relay: Geth should be started in `geth --external_signer localhost:8550`. + - Currently, the Geth APIs use `common.Address` in the arguments to transaction submission (e.g `to` field). This + type is 20 `bytes`, and is incapable of carrying checksum information. The signer uses `common.MixedcaseAddress`, which + retains the original input. + - The Geth api should switch to use the same type, and relay `to`-account verbatim to the external api. + +* [x] Storage + * [x] An encrypted key-value storage should be implemented + * See [rules.md](rules.md) for more info about this. + +* Another potential thing to introduce is pairing. + * To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API). + * Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests. + * This feature would make the addition of rules less dangerous. + +* Wallets / accounts. Add API methods for wallets. + +## Communication + +### External API + +The signer listens to HTTP requests on `rpcaddr`:`rpcport`, with the same JSONRPC standard as Geth. The messages are +expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification). + +Some of these call can require user interaction. Clients must be aware that responses +may be delayed significanlty or may never be received if a users decides to ignore the confirmation request. + +The External API is **untrusted** : it does not accept credentials over this api, nor does it expect +that requests have any authority. + +### UI API + +The signer has one native console-based UI, for operation without any standalone tools. +However, there is also an API to communicate with an external UI. To enable that UI, +the signer needs to be executed with the `--stdio-ui` option, which allocates the +`stdin`/`stdout` for the UI-api. + +An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`. + +The model is as follows: + +* The user starts the UI app (`pythonsigner.py`). +* The UI app starts the `signer` with `--stdio-ui`, and listens to the +process output for confirmation-requests. +* The `signer` opens the external http api. +* When the `signer` receives requests, it sends a `jsonrpc` request via `stdout`. +* The UI app prompts the user accordingly, and responds to the `signer` +* The `signer` signs (or not), and responds to the original request. + +## External API + +See the [external api changelog](extapi_changelog.md) for information about changes to this API. + +### Encoding +- number: positive integers that are hex encoded +- data: hex encoded data +- string: ASCII string + +All hex encoded values must be prefixed with `0x`. + +## Methods + +### account_new + +#### Create new password protected account + +The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory. +The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. + +#### Arguments + +None + +#### Result + - address [string]: account address that is derived from the generated key + - url [string]: location of the keyfile + +#### Sample call +```json +{ + "id": 0, + "jsonrpc": "2.0", + "method": "account_new", + "params": [] +} + +{ + "id": 0, + "jsonrpc": "2.0", + "result": { + "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", + "url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" + } +} +``` + +### account_list + +#### List available accounts + List all accounts that this signer currently manages + +#### Arguments + +None + +#### Result + - array with account records: + - account.address [string]: account address that is derived from the generated key + - account.type [string]: type of the + - account.url [string]: location of the account + +#### Sample call +```json +{ + "id": 1, + "jsonrpc": "2.0", + "method": "account_list" +} + +{ + "id": 1, + "jsonrpc": "2.0", + "result": [ + { + "address": "0xafb2f771f58513609765698f65d3f2f0224a956f", + "type": "account", + "url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f" + }, + { + "address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133", + "type": "account", + "url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133" + } + ] +} +``` + +### account_signTransaction + +#### Sign transactions + Signs a transactions and responds with the signed transaction in RLP encoded form. + +#### Arguments + 2. transaction object: + - `from` [address]: account to send the transaction from + - `to` [address]: receiver account. If omitted or `0x`, will cause contract creation. + - `gas` [number]: maximum amount of gas to burn + - `gasPrice` [number]: gas price + - `value` [number:optional]: amount of Wei to send with the transaction + - `data` [data:optional]: input data + - `nonce` [number]: account nonce + 3. method signature [string:optional] + - The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected. + + +#### Result + - signed transaction in RLP encoded form [data] + +#### Sample call +```json +{ + "id": 2, + "jsonrpc": "2.0", + "method": "account_signTransaction", + "params": [ + { + "from": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", + "gas": "0x55555", + "gasPrice": "0x1234", + "input": "0xabcd", + "nonce": "0x0", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x1234" + } + ] +} +``` +Response + +```json +{ + "jsonrpc": "2.0", + "id": 67, + "error": { + "code": -32000, + "message": "Request denied" + } +} +``` +#### Sample call with ABI-data + + +```json +{ + "jsonrpc": "2.0", + "method": "account_signTransaction", + "params": [ + { + "from": "0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "gas": "0x333", + "gasPrice": "0x1", + "nonce": "0x0", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" + }, + "safeSend(address)" + ], + "id": 67 +} +``` +Response + +```json +{ + "jsonrpc": "2.0", + "id": 67, + "result": { + "raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "tx": { + "nonce": "0x0", + "gasPrice": "0x1", + "gas": "0x333", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "value": "0x0", + "input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "v": "0x26", + "r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e", + "s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663", + "hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e" + } + } +} +``` + +Bash example: +```bash +#curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ + +{"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}} +``` + + +### account_sign + +#### Sign data + Signs a chunk of data and returns the calculated signature. + +#### Arguments + - account [address]: account to sign with + - data [data]: data to sign + +#### Result + - calculated signature [data] + +#### Sample call +```json +{ + "id": 3, + "jsonrpc": "2.0", + "method": "account_sign", + "params": [ + "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db", + "0xaabbccdd" + ] +} +``` +Response + +```json +{ + "id": 3, + "jsonrpc": "2.0", + "result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" +} +``` + +### account_ecRecover + +#### Recover address + Derive the address from the account that was used to sign data from the data and signature. + +#### Arguments + - data [data]: data that was signed + - signature [data]: the signature to verify + +#### Result + - derived account [address] + +#### Sample call +```json +{ + "id": 4, + "jsonrpc": "2.0", + "method": "account_ecRecover", + "params": [ + "0xaabbccdd", + "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c" + ] +} +``` +Response + +```json +{ + "id": 4, + "jsonrpc": "2.0", + "result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db" +} + +``` + +### account_import + +#### Import account + Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore + format. + +#### Arguments + - account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export) + +#### Result + - imported key [object]: + - key.address [address]: address of the imported key + - key.type [string]: type of the account + - key.url [string]: key URL + +#### Sample call +```json +{ + "id": 6, + "jsonrpc": "2.0", + "method": "account_import", + "params": [ + { + "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "401c39a7c7af0388491c3d3ecb39f532" + }, + "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" + }, + "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" + }, + "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", + "version": 3 + }, + ] +} +``` +Response + +```json +{ + "id": 6, + "jsonrpc": "2.0", + "result": { + "address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5", + "type": "account", + "url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5" + } +} +``` + +### account_export + +#### Export account from keystore + Export a private key from the keystore. The exported private key is encrypted with the original passphrase. When the + key is imported later this passphrase is required. + +#### Arguments + - account [address]: export private key that is associated with this account + +#### Result + - exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for + more information + +#### Sample call +```json +{ + "id": 5, + "jsonrpc": "2.0", + "method": "account_export", + "params": [ + "0xc7412fc59930fd90099c917a50e5f11d0934b2f5" + ] +} +``` +Response + +```json +{ + "id": 5, + "jsonrpc": "2.0", + "result": { + "address": "c7412fc59930fd90099c917a50e5f11d0934b2f5", + "crypto": { + "cipher": "aes-128-ctr", + "cipherparams": { + "iv": "401c39a7c7af0388491c3d3ecb39f532" + }, + "ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204", + "kdf": "scrypt", + "kdfparams": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a" + }, + "mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806" + }, + "id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9", + "version": 3 + } +} +``` + + + +## UI API + +These methods needs to be implemented by a UI listener. + +By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with +denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented. +See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'. + +All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key. + +See the [ui api changelog](intapi_changelog.md) for information about changes to this API. + +OBS! A slight deviation from `json` standard is in place: every request and response should be confined to a single line. +Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make +things simpler for both parties. + +### ApproveTx + +Invoked when there's a transaction for approval. + + +#### Sample call + +Here's a method invocation: +```bash + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ +``` + +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ApproveTx", + "params": [ + { + "transaction": { + "from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "gas": "0x333", + "gasPrice": "0x1", + "value": "0x0", + "nonce": "0x0", + "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "input": null + }, + "call_info": [ + { + "type": "WARNING", + "message": "Invalid checksum on to-address" + }, + { + "type": "Info", + "message": "safeSend(address: 0x0000000000000000000000000000000000000012)" + } + ], + "meta": { + "remote": "127.0.0.1:48486", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + } + ] +} + +``` + +The same method invocation, but with invalid data: +```bash + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000002000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/ +``` + +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "ApproveTx", + "params": [ + { + "transaction": { + "from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa", + "to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "gas": "0x333", + "gasPrice": "0x1", + "value": "0x0", + "nonce": "0x0", + "data": "0x4401a6e40000000000000002000000000000000000000000000000000000000000000012", + "input": null + }, + "call_info": [ + { + "type": "WARNING", + "message": "Invalid checksum on to-address" + }, + { + "type": "WARNING", + "message": "Transaction data did not match ABI-interface: WARNING: Supplied data is stuffed with extra data. \nWant 0000000000000002000000000000000000000000000000000000000000000012\nHave 0000000000000000000000000000000000000000000000000000000000000012\nfor method safeSend(address)" + } + ], + "meta": { + "remote": "127.0.0.1:48492", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + } + ] +} + + +``` + +One which has missing `to`, but with no `data`: + + +```json + +{ + "jsonrpc": "2.0", + "id": 3, + "method": "ApproveTx", + "params": [ + { + "transaction": { + "from": "", + "to": null, + "gas": "0x0", + "gasPrice": "0x0", + "value": "0x0", + "nonce": "0x0", + "data": null, + "input": null + }, + "call_info": [ + { + "type": "CRITICAL", + "message": "Tx will create contract with empty code!" + } + ], + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} +``` + +### ApproveExport + +Invoked when a request to export an account has been made. + +#### Sample call + +```json + +{ + "jsonrpc": "2.0", + "id": 7, + "method": "ApproveExport", + "params": [ + { + "address": "0x0000000000000000000000000000000000000000", + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + +### ApproveListing + +Invoked when a request for account listing has been made. + +#### Sample call + +```json + +{ + "jsonrpc": "2.0", + "id": 5, + "method": "ApproveListing", + "params": [ + { + "accounts": [ + { + "type": "Account", + "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42", + "address": "0x123409812340981234098123409812deadbeef42" + }, + { + "type": "Account", + "url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42", + "address": "0xcafebabedeadbeef34098123409812deadbeef42" + } + ], + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + + +### ApproveSignData + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "ApproveSignData", + "params": [ + { + "address": "0x123409812340981234098123409812deadbeef42", + "raw_data": "0x01020304", + "message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004", + "hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310", + "meta": { + "remote": "signer binary", + "local": "main", + "scheme": "in-proc" + } + } + ] +} + +``` + +### ShowInfo + +The UI should show the info to the user. Does not expect response. + +#### Sample call + +```json +{ + "jsonrpc": "2.0", + "id": 9, + "method": "ShowInfo", + "params": [ + { + "text": "Tests completed" + } + ] +} + +``` + +### ShowError + +The UI should show the info to the user. Does not expect response. + +```json + +{ + "jsonrpc": "2.0", + "id": 2, + "method": "ShowError", + "params": [ + { + "text": "Testing 'ShowError'" + } + ] +} + +``` + +### OnApproved + +`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions. + +When implementing rate-limited rules, this callback should be used. + +TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`. + +### OnSignerStartup + +This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api, +in k/v-form. + +Example call: +```json + +{ + "jsonrpc": "2.0", + "id": 1, + "method": "OnSignerStartup", + "params": [ + { + "info": { + "extapi_http": "http://localhost:8550", + "extapi_ipc": null, + "extapi_version": "2.0.0", + "intapi_version": "1.2.0" + } + } + ] +} + +``` + + +### Rules for UI apis + +A UI should conform to the following rules. + +* A UI MUST NOT load any external resources that were not embedded/part of the UI package. + * For example, not load icons, stylesheets from the internet + * Not load files from the filesystem, unless they reside in the same local directory (e.g. config files) +* A Graphical UI MUST show the blocky-identicon for ethereum addresses. +* A UI MUST warn display approproate warning if the destination-account is formatted with invalid checksum. +* A UI MUST NOT open any ports or services + * The signer opens the public port +* A UI SHOULD verify the permissions on the signer binary, and refuse to execute or warn if permissions allow non-user write. +* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed +* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts + * The signer provides accounts +* A UI SHOULD, to the best extent possible, use static linking / bundling, so that requried libraries are bundled +along with the UI. + + +### UI Implementations + +There are a couple of implementation for a UI. We'll try to keep this list up to date. + +| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters| +| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- | +| QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)| +| GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: | +| Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: | diff --git a/cmd/clef/docs/qubes/clef_qubes_http.png b/cmd/clef/docs/qubes/clef_qubes_http.png new file mode 100644 index 0000000000..a641e1987f Binary files /dev/null and b/cmd/clef/docs/qubes/clef_qubes_http.png differ diff --git a/cmd/clef/docs/qubes/clef_qubes_qrexec.png b/cmd/clef/docs/qubes/clef_qubes_qrexec.png new file mode 100644 index 0000000000..f57fc8933e Binary files /dev/null and b/cmd/clef/docs/qubes/clef_qubes_qrexec.png differ diff --git a/cmd/clef/docs/qubes/qrexec-example.png b/cmd/clef/docs/qubes/qrexec-example.png new file mode 100644 index 0000000000..0d86fde19d Binary files /dev/null and b/cmd/clef/docs/qubes/qrexec-example.png differ diff --git a/cmd/clef/docs/qubes/qubes-client.py b/cmd/clef/docs/qubes/qubes-client.py new file mode 100644 index 0000000000..93a74b899b --- /dev/null +++ b/cmd/clef/docs/qubes/qubes-client.py @@ -0,0 +1,23 @@ +""" +This implements a dispatcher which listens to localhost:8550, and proxies +requests via qrexec to the service qubes.EthSign on a target domain +""" + +import http.server +import socketserver,subprocess + +PORT=8550 +TARGET_DOMAIN= 'debian-work' + +class Dispatcher(http.server.BaseHTTPRequestHandler): + def do_POST(self): + post_data = self.rfile.read(int(self.headers['Content-Length'])) + p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE) + output = p.communicate(post_data)[0] + self.wfile.write(output) + + +with socketserver.TCPServer(("",PORT), Dispatcher) as httpd: + print("Serving at port", PORT) + httpd.serve_forever() + diff --git a/cmd/clef/docs/qubes/qubes.Clefsign b/cmd/clef/docs/qubes/qubes.Clefsign new file mode 100644 index 0000000000..9b5af7b4fe --- /dev/null +++ b/cmd/clef/docs/qubes/qubes.Clefsign @@ -0,0 +1,16 @@ +#!/bin/bash + +SIGNER_BIN="/home/user/tools/clef/clef" +SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN" + +# Start clef if not already started +if [ ! -S /home/user/.clef/clef.ipc ]; then + $SIGNER_CMD & + sleep 1 +fi + +# Should be started by now +if [ -S /home/user/.clef/clef.ipc ]; then + # Post incoming request to HTTP channel + curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null +fi diff --git a/cmd/clef/docs/qubes/qubes_newaccount-1.png b/cmd/clef/docs/qubes/qubes_newaccount-1.png new file mode 100644 index 0000000000..598dbbee7a Binary files /dev/null and b/cmd/clef/docs/qubes/qubes_newaccount-1.png differ diff --git a/cmd/clef/docs/qubes/qubes_newaccount-2.png b/cmd/clef/docs/qubes/qubes_newaccount-2.png new file mode 100644 index 0000000000..cd762a1934 Binary files /dev/null and b/cmd/clef/docs/qubes/qubes_newaccount-2.png differ diff --git a/cmd/clef/docs/setup.md b/cmd/clef/docs/setup.md new file mode 100644 index 0000000000..33d2b0381f --- /dev/null +++ b/cmd/clef/docs/setup.md @@ -0,0 +1,198 @@ +# Setting up Clef + +This document describes how Clef can be used in a more secure manner than executing it from your everyday laptop, +in order to ensure that the keys remain safe in the event that your computer should get compromised. + +## Qubes OS + + +### Background + +The Qubes operating system is based around virtual machines (qubes), where a set of virtual machines are configured, typically for +different purposes such as: + +- personal + - Your personal email, browsing etc +- work + - Work email etc +- vault + - a VM without network access, where gpg-keys and/or keepass credentials are stored. + +A couple of dedicated virtual machines handle externalities: + +- sys-net provides networking to all other (network-enabled) machines +- sys-firewall handles firewall rules +- sys-usb handles USB devices, and can map usb-devices to certain qubes. + +The goal of this document is to describe how we can set up clef to provide secure transaction +signing from a `vault` vm, to another networked qube which runs Dapps. + +### Setup + +There are two ways that this can be achieved: integrated via Qubes or integrated via networking. + + +#### 1. Qubes Integrated + +Qubes provdes a facility for inter-qubes communication via `qrexec`. A qube can request to make a cross-qube RPC request +to another qube. The OS then asks the user if the call is permitted. + +![Example](qubes/qrexec-example.png) + +A policy-file can be created to allow such interaction. On the `target` domain, a service is invoked which can read the +`stdin` from the `client` qube. + +This is how [Split GPG](https://www.qubes-os.org/doc/split-gpg/) is implemented. We can set up Clef the same way: + +##### Server + +![Clef via qrexec](qubes/clef_qubes_qrexec.png) + +On the `target` qubes, we need to define the rpc service. + +[qubes.Clefsign](qubes/qubes.Clefsign): + +```bash +#!/bin/bash + +SIGNER_BIN="/home/user/tools/clef/clef" +SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN" + +# Start clef if not already started +if [ ! -S /home/user/.clef/clef.ipc ]; then + $SIGNER_CMD & + sleep 1 +fi + +# Should be started by now +if [ -S /home/user/.clef/clef.ipc ]; then + # Post incoming request to HTTP channel + curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null +fi + +``` +This RPC service is not complete (see notes about HTTP headers below), but works as a proof-of-concept. +It will forward the data received on `stdin` (forwarded by the OS) to Clef's HTTP channel. + +It would have been possible to send data directly to the `/home/user/.clef/.clef.ipc` +socket via e.g `nc -U /home/user/.clef/clef.ipc`, but the reason for sending the request +data over `HTTP` instead of `IPC` is that we want the ability to forward `HTTP` headers. + +To enable the service: + +``` bash +sudo cp qubes.Clefsign /etc/qubes-rpc/ +sudo chmod +x /etc/qubes-rpc/ qubes.Clefsign +``` + +This setup uses [gtksigner](https://github.com/holiman/gtksigner), which is a very minimal GTK-based UI that works well +with minimal requirements. + +##### Client + + +On the `client` qube, we need to create a listener which will receive the request from the Dapp, and proxy it. + + +[qubes-client.py](qubes/client/qubes-client.py): + +```python + +""" +This implements a dispatcher which listens to localhost:8550, and proxies +requests via qrexec to the service qubes.EthSign on a target domain +""" + +import http.server +import socketserver,subprocess + +PORT=8550 +TARGET_DOMAIN= 'debian-work' + +class Dispatcher(http.server.BaseHTTPRequestHandler): + def do_POST(self): + post_data = self.rfile.read(int(self.headers['Content-Length'])) + p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE) + output = p.communicate(post_data)[0] + self.wfile.write(output) + + +with socketserver.TCPServer(("",PORT), Dispatcher) as httpd: + print("Serving at port", PORT) + httpd.serve_forever() + + +``` + +#### Testing + +To test the flow, if we have set up `debian-work` as the `target`, we can do + +```bash +$ cat newaccnt.json +{ "id": 0, "jsonrpc": "2.0","method": "account_new","params": []} + +$ cat newaccnt.json| qrexec-client-vm debian-work qubes.Clefsign +``` + +This should pop up first a dialog to allow the IPC call: + +![one](qubes/qubes_newaccount-1.png) + +Followed by a GTK-dialog to approve the operation + +![two](qubes/qubes_newaccount-2.png) + +To test the full flow, we use the client wrapper. Start it on the `client` qube: +``` +[user@work qubes]$ python3 qubes-client.py +``` + +Make the request over http (`client` qube): +``` +[user@work clef]$ cat newaccnt.json | curl -X POST -d @- http://localhost:8550 +``` +And it should show the same popups again. + +##### Pros and cons + +The benefits of this setup are: + +- This is the qubes-os intended model for inter-qube communication, +- and thus benefits from qubes-os dialogs and policies for user approval + +However, it comes with a couple of drawbacks: + +- The `qubes-gpg-client` must forward the http request via RPC to the `target` qube. When doing so, the proxy + will either drop important headers, or replace them. + - The `Host` header is most likely `localhost` + - The `Origin` header must be forwarded + - Information about the remote ip must be added as a `X-Forwarded-For`. However, Clef cannot always trust an `XFF` header, + since malicious clients may lie about `XFF` in order to fool the http server into believing it comes from another address. +- Even with a policy in place to allow rpc-calls between `caller` and `target`, there will be several popups: + - One qubes-specific where the user specifies the `target` vm + - One clef-specific to approve the transaction + + +#### 2. Network integrated + +The second way to set up Clef on a qubes system is to allow networking, and have Clef listen to a port which is accessible +form other qubes. + +![Clef via http](qubes/clef_qubes_http.png) + + + + +## USBArmory + +The [USB armory](https://inversepath.com/usbarmory) is an open source hardware design with an 800 Mhz ARM processor. It is a pocket-size +computer. When inserted into a laptop, it identifies itself as a USB network interface, basically adding another network +to your computer. Over this new network interface, you can SSH into the device. + +Running Clef off a USB armory means that you can use the armory as a very versatile offline computer, which only +ever connects to a local network between your computer and the device itself. + +Needless to say, the while this model should be fairly secure against remote attacks, an attacker with physical access +to the USB Armory would trivially be able to extract the contents of the device filesystem. + diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md new file mode 100644 index 0000000000..2014e90ae4 --- /dev/null +++ b/cmd/clef/extapi_changelog.md @@ -0,0 +1,25 @@ +### Changelog for external API + + + +#### 2.0.0 + +* Commit `73abaf04b1372fa4c43201fb1b8019fe6b0a6f8d`, move `from` into `transaction` object in `signTransaction`. This +makes the `accounts_signTransaction` identical to the old `eth_signTransaction`. + + +#### 1.0.0 + +Initial release. + +### Versioning + +The API uses [semantic versioning](https://semver.org/). + +TLDR; Given a version number MAJOR.MINOR.PATCH, increment the: + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md new file mode 100644 index 0000000000..7d2a897ea2 --- /dev/null +++ b/cmd/clef/intapi_changelog.md @@ -0,0 +1,86 @@ +### Changelog for internal API (ui-api) + +### 2.0.0 + +* Modify how `call_info` on a transaction is conveyed. New format: + +``` +{ + "jsonrpc": "2.0", + "id": 2, + "method": "ApproveTx", + "params": [ + { + "transaction": { + "from": "0x82A2A876D39022B3019932D30Cd9c97ad5616813", + "to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0", + "gas": "0x333", + "gasPrice": "0x123", + "value": "0x10", + "nonce": "0x0", + "data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012", + "input": null + }, + "call_info": [ + { + "type": "WARNING", + "message": "Invalid checksum on to-address" + }, + { + "type": "WARNING", + "message": "Tx contains data, but provided ABI signature could not be matched: Did not match: test (0 matches)" + } + ], + "meta": { + "remote": "127.0.0.1:54286", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + } + ] +} +``` + +#### 1.2.0 + +* Add `OnStartup` method, to provide the UI with information about what API version +the signer uses (both internal and external) aswell as build-info and external api. + +Example call: +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "OnSignerStartup", + "params": [ + { + "info": { + "extapi_http": "http://localhost:8550", + "extapi_ipc": null, + "extapi_version": "2.0.0", + "intapi_version": "1.2.0" + } + } + ] +} +``` + +#### 1.1.0 + +* Add `OnApproved` method + +#### 1.0.0 + +Initial release. + +### Versioning + +The API uses [semantic versioning](https://semver.org/). + +TLDR; Given a version number MAJOR.MINOR.PATCH, increment the: + +* MAJOR version when you make incompatible API changes, +* MINOR version when you add functionality in a backwards-compatible manner, and +* PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. diff --git a/cmd/clef/main.go b/cmd/clef/main.go new file mode 100644 index 0000000000..348bcb22f6 --- /dev/null +++ b/cmd/clef/main.go @@ -0,0 +1,640 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +// signer is a utility that can be used so sign transactions and +// arbitrary data. +package main + +import ( + "bufio" + "context" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "os/signal" + "os/user" + "path/filepath" + "runtime" + "strings" + + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/signer/core" + "github.com/ethereum/go-ethereum/signer/rules" + "github.com/ethereum/go-ethereum/signer/storage" + "gopkg.in/urfave/cli.v1" +) + +// ExternalAPIVersion -- see extapi_changelog.md +const ExternalAPIVersion = "2.0.0" + +// InternalAPIVersion -- see intapi_changelog.md +const InternalAPIVersion = "2.0.0" + +const legalWarning = ` +WARNING! + +Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there +are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software +unless you agree to take full responsibility for doing so, and know what you are doing. + +TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! + +` + +var ( + logLevelFlag = cli.IntFlag{ + Name: "loglevel", + Value: 4, + Usage: "log level to emit to the screen", + } + keystoreFlag = cli.StringFlag{ + Name: "keystore", + Value: filepath.Join(node.DefaultDataDir(), "keystore"), + Usage: "Directory for the keystore", + } + configdirFlag = cli.StringFlag{ + Name: "configdir", + Value: DefaultConfigDir(), + Usage: "Directory for Clef configuration", + } + rpcPortFlag = cli.IntFlag{ + Name: "rpcport", + Usage: "HTTP-RPC server listening port", + Value: node.DefaultHTTPPort + 5, + } + signerSecretFlag = cli.StringFlag{ + Name: "signersecret", + Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash", + } + dBFlag = cli.StringFlag{ + Name: "4bytedb", + Usage: "File containing 4byte-identifiers", + Value: "./4byte.json", + } + customDBFlag = cli.StringFlag{ + Name: "4bytedb-custom", + Usage: "File used for writing new 4byte-identifiers submitted via API", + Value: "./4byte-custom.json", + } + auditLogFlag = cli.StringFlag{ + Name: "auditlog", + Usage: "File used to emit audit logs. Set to \"\" to disable", + Value: "audit.log", + } + ruleFlag = cli.StringFlag{ + Name: "rules", + Usage: "Enable rule-engine", + Value: "rules.json", + } + stdiouiFlag = cli.BoolFlag{ + Name: "stdio-ui", + Usage: "Use STDIN/STDOUT as a channel for an external UI. " + + "This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " + + "interface, and can be used when Clef is started by an external process.", + } + testFlag = cli.BoolFlag{ + Name: "stdio-ui-test", + Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.", + } + app = cli.NewApp() + initCommand = cli.Command{ + Action: utils.MigrateFlags(initializeSecrets), + Name: "init", + Usage: "Initialize the signer, generate secret storage", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + }, + Description: ` +The init command generates a master seed which Clef can use to store credentials and data needed for +the rule-engine to work.`, + } + attestCommand = cli.Command{ + Action: utils.MigrateFlags(attestFile), + Name: "attest", + Usage: "Attest that a js-file is to be used", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + signerSecretFlag, + }, + Description: ` +The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of +incoming requests. + +Whenever you make an edit to the rule file, you need to use attestation to tell +Clef that the file is 'safe' to execute.`, + } + + addCredentialCommand = cli.Command{ + Action: utils.MigrateFlags(addCredential), + Name: "addpw", + Usage: "Store a credential for a keystore file", + ArgsUsage: "
", + Flags: []cli.Flag{ + logLevelFlag, + configdirFlag, + signerSecretFlag, + }, + Description: ` +The addpw command stores a password for a given address (keyfile). If you invoke it with only one parameter, it will +remove any stored credential for that address (keyfile) +`, + } +) + +func init() { + app.Name = "Clef" + app.Usage = "Manage Ethereum account operations" + app.Flags = []cli.Flag{ + logLevelFlag, + keystoreFlag, + configdirFlag, + utils.NetworkIdFlag, + utils.LightKDFFlag, + utils.NoUSBFlag, + utils.RPCListenAddrFlag, + utils.RPCVirtualHostsFlag, + utils.IPCDisabledFlag, + utils.IPCPathFlag, + utils.RPCEnabledFlag, + rpcPortFlag, + signerSecretFlag, + dBFlag, + customDBFlag, + auditLogFlag, + ruleFlag, + stdiouiFlag, + testFlag, + } + app.Action = signer + app.Commands = []cli.Command{initCommand, attestCommand, addCredentialCommand} + +} +func main() { + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func initializeSecrets(c *cli.Context) error { + if err := initialize(c); err != nil { + return err + } + configDir := c.String(configdirFlag.Name) + + masterSeed := make([]byte, 256) + n, err := io.ReadFull(rand.Reader, masterSeed) + if err != nil { + return err + } + if n != len(masterSeed) { + return fmt.Errorf("failed to read enough random") + } + err = os.Mkdir(configDir, 0700) + if err != nil && !os.IsExist(err) { + return err + } + location := filepath.Join(configDir, "secrets.dat") + if _, err := os.Stat(location); err == nil { + return fmt.Errorf("file %v already exists, will not overwrite", location) + } + err = ioutil.WriteFile(location, masterSeed, 0700) + if err != nil { + return err + } + fmt.Printf("A master seed has been generated into %s\n", location) + fmt.Printf(` +This is required to be able to store credentials, such as : +* Passwords for keystores (used by rule engine) +* Storage for javascript rules +* Hash of rule-file + +You should treat that file with utmost secrecy, and make a backup of it. +NOTE: This file does not contain your accounts. Those need to be backed up separately! + +`) + return nil +} +func attestFile(ctx *cli.Context) error { + if len(ctx.Args()) < 1 { + utils.Fatalf("This command requires an argument.") + } + if err := initialize(ctx); err != nil { + return err + } + + stretchedKey, err := readMasterKey(ctx) + if err != nil { + utils.Fatalf(err.Error()) + } + configDir := ctx.String(configdirFlag.Name) + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + confKey := crypto.Keccak256([]byte("config"), stretchedKey) + + // Initialize the encrypted storages + configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey) + val := ctx.Args().First() + configStorage.Put("ruleset_sha256", val) + log.Info("Ruleset attestation updated", "sha256", val) + return nil +} + +func addCredential(ctx *cli.Context) error { + if len(ctx.Args()) < 1 { + utils.Fatalf("This command requires at leaste one argument.") + } + if err := initialize(ctx); err != nil { + return err + } + + stretchedKey, err := readMasterKey(ctx) + if err != nil { + utils.Fatalf(err.Error()) + } + configDir := ctx.String(configdirFlag.Name) + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) + + // Initialize the encrypted storages + pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) + key := ctx.Args().First() + value := "" + if len(ctx.Args()) > 1 { + value = ctx.Args().Get(1) + } + pwStorage.Put(key, value) + log.Info("Credential store updated", "key", key) + return nil +} + +func initialize(c *cli.Context) error { + // Set up the logger to print everything + logOutput := os.Stdout + if c.Bool(stdiouiFlag.Name) { + logOutput = os.Stderr + // If using the stdioui, we can't do the 'confirm'-flow + fmt.Fprintf(logOutput, legalWarning) + } else { + if !confirm(legalWarning) { + return fmt.Errorf("aborted by user") + } + } + + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true)))) + return nil +} + +func signer(c *cli.Context) error { + if err := initialize(c); err != nil { + return err + } + var ( + ui core.SignerUI + ) + if c.Bool(stdiouiFlag.Name) { + log.Info("Using stdin/stdout as UI-channel") + ui = core.NewStdIOUI() + } else { + log.Info("Using CLI as UI-channel") + ui = core.NewCommandlineUI() + } + db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name)) + if err != nil { + utils.Fatalf(err.Error()) + } + log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb")) + + var ( + api core.ExternalAPI + ) + + configDir := c.String(configdirFlag.Name) + if stretchedKey, err := readMasterKey(c); err != nil { + log.Info("No master seed provided, rules disabled") + } else { + + if err != nil { + utils.Fatalf(err.Error()) + } + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) + + // Generate domain specific keys + pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) + jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey) + confkey := crypto.Keccak256([]byte("config"), stretchedKey) + + // Initialize the encrypted storages + pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey) + jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey) + configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) + + //Do we have a rule-file? + ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name)) + if err != nil { + log.Info("Could not load rulefile, rules not enabled", "file", "rulefile") + } else { + hasher := sha256.New() + hasher.Write(ruleJS) + shasum := hasher.Sum(nil) + storedShasum := configStorage.Get("ruleset_sha256") + if storedShasum != hex.EncodeToString(shasum) { + log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum) + } else { + // Initialize rules + ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage) + if err != nil { + utils.Fatalf(err.Error()) + } + ruleEngine.Init(string(ruleJS)) + ui = ruleEngine + log.Info("Rule engine configured", "file", c.String(ruleFlag.Name)) + } + } + } + + apiImpl := core.NewSignerAPI( + c.Int64(utils.NetworkIdFlag.Name), + c.String(keystoreFlag.Name), + c.Bool(utils.NoUSBFlag.Name), + ui, db, + c.Bool(utils.LightKDFFlag.Name)) + + api = apiImpl + + // Audit logging + if logfile := c.String(auditLogFlag.Name); logfile != "" { + api, err = core.NewAuditLogger(logfile, api) + if err != nil { + utils.Fatalf(err.Error()) + } + log.Info("Audit logs configured", "file", logfile) + } + // register signer API with server + var ( + extapiURL = "n/a" + ipcapiURL = "n/a" + ) + rpcAPI := []rpc.API{ + { + Namespace: "account", + Public: true, + Service: api, + Version: "1.0"}, + } + if c.Bool(utils.RPCEnabledFlag.Name) { + + vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) + cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) + + // start http server + httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) + listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts) + if err != nil { + utils.Fatalf("Could not start RPC api: %v", err) + } + extapiURL = fmt.Sprintf("http://%s", httpEndpoint) + log.Info("HTTP endpoint opened", "url", extapiURL) + + defer func() { + listener.Close() + log.Info("HTTP endpoint closed", "url", httpEndpoint) + }() + + } + if !c.Bool(utils.IPCDisabledFlag.Name) { + if c.IsSet(utils.IPCPathFlag.Name) { + ipcapiURL = c.String(utils.IPCPathFlag.Name) + } else { + ipcapiURL = filepath.Join(configDir, "clef.ipc") + } + + listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI) + if err != nil { + utils.Fatalf("Could not start IPC api: %v", err) + } + log.Info("IPC endpoint opened", "url", ipcapiURL) + defer func() { + listener.Close() + log.Info("IPC endpoint closed", "url", ipcapiURL) + }() + + } + + if c.Bool(testFlag.Name) { + log.Info("Performing UI test") + go testExternalUI(apiImpl) + } + ui.OnSignerStartup(core.StartupInfo{ + Info: map[string]interface{}{ + "extapi_version": ExternalAPIVersion, + "intapi_version": InternalAPIVersion, + "extapi_http": extapiURL, + "extapi_ipc": ipcapiURL, + }, + }) + + abortChan := make(chan os.Signal) + signal.Notify(abortChan, os.Interrupt) + + sig := <-abortChan + log.Info("Exiting...", "signal", sig) + + return nil +} + +// splitAndTrim splits input separated by a comma +// and trims excessive white space from the substrings. +func splitAndTrim(input string) []string { + result := strings.Split(input, ",") + for i, r := range result { + result[i] = strings.TrimSpace(r) + } + return result +} + +// DefaultConfigDir is the default config directory to use for the vaults and other +// persistence requirements. +func DefaultConfigDir() string { + // Try to place the data folder in the user's home dir + home := homeDir() + if home != "" { + if runtime.GOOS == "darwin" { + return filepath.Join(home, "Library", "Signer") + } else if runtime.GOOS == "windows" { + return filepath.Join(home, "AppData", "Roaming", "Signer") + } else { + return filepath.Join(home, ".clef") + } + } + // As we cannot guess a stable location, return empty and handle later + return "" +} + +func homeDir() string { + if home := os.Getenv("HOME"); home != "" { + return home + } + if usr, err := user.Current(); err == nil { + return usr.HomeDir + } + return "" +} +func readMasterKey(ctx *cli.Context) ([]byte, error) { + var ( + file string + configDir = ctx.String(configdirFlag.Name) + ) + if ctx.IsSet(signerSecretFlag.Name) { + file = ctx.String(signerSecretFlag.Name) + } else { + file = filepath.Join(configDir, "secrets.dat") + } + if err := checkFile(file); err != nil { + return nil, err + } + masterKey, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + if len(masterKey) < 256 { + return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey)) + } + // Create vault location + vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10])) + err = os.Mkdir(vaultLocation, 0700) + if err != nil && !os.IsExist(err) { + return nil, err + } + //!TODO, use KDF to stretch the master key + // stretched_key := stretch_key(master_key) + + return masterKey, nil +} + +// checkFile is a convenience function to check if a file +// * exists +// * is mode 0600 +func checkFile(filename string) error { + info, err := os.Stat(filename) + if err != nil { + return fmt.Errorf("failed stat on %s: %v", filename, err) + } + // Check the unix permission bits + if info.Mode().Perm()&077 != 0 { + return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String()) + } + return nil +} + +// confirm displays a text and asks for user confirmation +func confirm(text string) bool { + fmt.Printf(text) + fmt.Printf("\nEnter 'ok' to proceed:\n>") + + text, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + log.Crit("Failed to read user input", "err", err) + } + + if text := strings.TrimSpace(text); text == "ok" { + return true + } + return false +} + +func testExternalUI(api *core.SignerAPI) { + + ctx := context.WithValue(context.Background(), "remote", "clef binary") + ctx = context.WithValue(ctx, "scheme", "in-proc") + ctx = context.WithValue(ctx, "local", "main") + + errs := make([]string, 0) + + api.UI.ShowInfo("Testing 'ShowInfo'") + api.UI.ShowError("Testing 'ShowError'") + + checkErr := func(method string, err error) { + if err != nil && err != core.ErrRequestDenied { + errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error())) + } + } + var err error + + _, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil) + checkErr("SignTransaction", err) + _, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304")) + checkErr("Sign", err) + _, err = api.List(ctx) + checkErr("List", err) + _, err = api.New(ctx) + checkErr("New", err) + _, err = api.Export(ctx, common.Address{}) + checkErr("Export", err) + _, err = api.Import(ctx, json.RawMessage{}) + checkErr("Import", err) + + api.UI.ShowInfo("Tests completed") + + if len(errs) > 0 { + log.Error("Got errors") + for _, e := range errs { + log.Error(e) + } + } else { + log.Info("No errors") + } + +} + +/** +//Create Account + +curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550 + +// List accounts + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/ + +// Make Transaction +// safeSend(0x12) +// 4401a6e40000000000000000000000000000000000000000000000000000000000000012 + +// supplied abi +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/ + +// Not supplied +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/ + +// Sign data + +curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_sign","params":["0x694267f14675d7e1b9494fd8d72fefe1755710fa","bazonk gaz baz"],"id":67}' http://localhost:8550/ + + +**/ diff --git a/cmd/clef/pythonsigner.py b/cmd/clef/pythonsigner.py new file mode 100644 index 0000000000..46fa23bd8c --- /dev/null +++ b/cmd/clef/pythonsigner.py @@ -0,0 +1,179 @@ +import os,sys, subprocess +from tinyrpc.transports import ServerTransport +from tinyrpc.protocols.jsonrpc import JSONRPCProtocol +from tinyrpc.dispatch import public,RPCDispatcher +from tinyrpc.server import RPCServer + +""" This is a POC example of how to write a custom UI for Clef. The UI starts the +clef process with the '--stdio-ui' option, and communicates with clef using standard input / output. + +The standard input/output is a relatively secure way to communicate, as it does not require opening any ports +or IPC files. Needless to say, it does not protect against memory inspection mechanisms where an attacker +can access process memory.""" + +try: + import urllib.parse as urlparse +except ImportError: + import urllib as urlparse + +class StdIOTransport(ServerTransport): + """ Uses std input/output for RPC """ + def receive_message(self): + return None, urlparse.unquote(sys.stdin.readline()) + + def send_reply(self, context, reply): + print(reply) + +class PipeTransport(ServerTransport): + """ Uses std a pipe for RPC """ + + def __init__(self,input, output): + self.input = input + self.output = output + + def receive_message(self): + data = self.input.readline() + print(">> {}".format( data)) + return None, urlparse.unquote(data) + + def send_reply(self, context, reply): + print("<< {}".format( reply)) + self.output.write(reply) + self.output.write("\n") + +class StdIOHandler(): + + def __init__(self): + pass + + @public + def ApproveTx(self,req): + """ + Example request: + { + "jsonrpc": "2.0", + "method": "ApproveTx", + "params": [{ + "transaction": { + "to": "0xae967917c465db8578ca9024c205720b1a3651A9", + "gas": "0x333", + "gasPrice": "0x123", + "value": "0x10", + "data": "0xd7a5865800000000000000000000000000000000000000000000000000000000000000ff", + "nonce": "0x0" + }, + "from": "0xAe967917c465db8578ca9024c205720b1a3651A9", + "call_info": "Warning! Could not validate ABI-data against calldata\nSupplied ABI spec does not contain method signature in data: 0xd7a58658", + "meta": { + "remote": "127.0.0.1:34572", + "local": "localhost:8550", + "scheme": "HTTP/1.1" + } + }], + "id": 1 + } + + :param transaction: transaction info + :param call_info: info abou the call, e.g. if ABI info could not be + :param meta: metadata about the request, e.g. where the call comes from + :return: + """ + transaction = req.get('transaction') + _from = req.get('from') + call_info = req.get('call_info') + meta = req.get('meta') + + return { + "approved" : False, + #"transaction" : transaction, + # "from" : _from, +# "password" : None, + } + + @public + def ApproveSignData(self, req): + """ Example request + + """ + return {"approved": False, "password" : None} + + @public + def ApproveExport(self, req): + """ Example request + + """ + return {"approved" : False} + + @public + def ApproveImport(self, req): + """ Example request + + """ + return { "approved" : False, "old_password": "", "new_password": ""} + + @public + def ApproveListing(self, req): + """ Example request + + """ + return {'accounts': []} + + @public + def ApproveNewAccount(self, req): + """ + Example request + + :return: + """ + return {"approved": False, + #"password": "" + } + + @public + def ShowError(self,message = {}): + """ + Example request: + + {"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowError'"},"id":1} + + :param message: to show + :return: nothing + """ + if 'text' in message.keys(): + sys.stderr.write("Error: {}\n".format( message['text'])) + return + + @public + def ShowInfo(self,message = {}): + """ + Example request + {"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowInfo'"},"id":0} + + :param message: to display + :return:nothing + """ + + if 'text' in message.keys(): + sys.stdout.write("Error: {}\n".format( message['text'])) + return + +def main(args): + + cmd = ["./clef", "--stdio-ui"] + if len(args) > 0 and args[0] == "test": + cmd.extend(["--stdio-ui-test"]) + print("cmd: {}".format(" ".join(cmd))) + dispatcher = RPCDispatcher() + dispatcher.register_instance(StdIOHandler(), '') + # line buffered + p = subprocess.Popen(cmd, bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + rpc_server = RPCServer( + PipeTransport(p.stdout, p.stdin), + JSONRPCProtocol(), + dispatcher + ) + rpc_server.serve_forever() + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/cmd/clef/rules.md b/cmd/clef/rules.md new file mode 100644 index 0000000000..327ba765c5 --- /dev/null +++ b/cmd/clef/rules.md @@ -0,0 +1,236 @@ +# Rules + +The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto) + +It enables usecases like the following: + +* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period +* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei` + +The two main features that are required for this to work well are; + +1. Rule Implementation: how to create, manage and interpret rules in a flexible but secure manner +2. Credential managements and credentials; how to provide auto-unlock without exposing keys unnecessarily. + +The section below deals with both of them + +## Rule Implementation + +A ruleset file is implemented as a `js` file. Under the hood, the ruleset-engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods +defined in the UI protocol. Example: + +```javascript + +function asBig(str){ + if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)} + return new BigNumber(str) +} + +// Approve transactions to a certain contract if value is below a certain limit +function ApproveTx(req){ + + var limit = big.Newint("0xb1a2bc2ec50000") + var value = asBig(req.transaction.value); + + if(req.transaction.to.toLowerCase()=="0xae967917c465db8578ca9024c205720b1a3651a9") + && value.lt(limit) ){ + return "Approve" + } + // If we return "Reject", it will be rejected. + // By not returning anything, it will be passed to the next UI, for manual processing +} + +//Approve listings if request made from IPC +function ApproveListing(req){ + if (req.metadata.scheme == "ipc"){ return "Approve"} +} + +``` + +Whenever the external API is called (and the ruleset is enabled), the `signer` calls the UI, which is an instance of a ruleset-engine. The ruleset-engine +invokes the corresponding method. In doing so, there are three possible outcomes: + +1. JS returns "Approve" + * Auto-approve request +2. JS returns "Reject" + * Auto-reject request +3. Error occurs, or something else is returned + * Pass on to `next` ui: the regular UI channel. + +A more advanced example can be found below, "Example 1: ruleset for a rate-limited window", using `storage` to `Put` and `Get` `string`s by key. + +* At the time of writing, storage only exists as an ephemeral unencrypted implementation, to be used during testing. + +### Things to note + +The Otto vm has a few [caveats](https://github.com/robertkrimen/otto): + +* "use strict" will parse, but does nothing. +* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification. +* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported. + +Additionally, a few more have been added + +* The rule execution cannot load external javascript files. +* The only preloaded libary is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the github repository. +* Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data. +* Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes. +* The JS engine has access to `storage` and `console`. + +#### Security considerations + +##### Security of ruleset + +Some security precautions can be made, such as: + +* Never load `ruleset.js` unless the file is `readonly` (`r-??-??-?`). If the user wishes to modify the ruleset, he must make it writeable and then set back to readonly. + * This is to prevent attacks where files are dropped on the users disk. +* Since we're going to have to have some form of secure storage (not defined in this section), we could also store the `sha3` of the `ruleset.js` file in there. + * If the user wishes to modify the ruleset, he'd then have to perform e.g. `signer --attest /path/to/ruleset --credential ` + +##### Security of implementation + +The drawbacks of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement, since it's already +implemented for `geth`. There are no known security vulnerabilities in, nor have we had any security-problems with it so far. + +The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered +an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit +to be gained from attacking the actual `signer` process from the `js` side would be if it could somehow extract cryptographic keys from memory. + +##### Security in usability + +Javascript is flexible, but also easy to get wrong, especially when users assume that `js` can handle large integers natively. Typical errors +include trying to multiply `gasCost` with `gas` without using `bigint`:s. + +It's unclear whether any other DSL could be more secure; since there's always the possibility of erroneously implementing a rule. + + +## Credential management + +The ability to auto-approve transaction means that the signer needs to have necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass). + +### Example implementation + +Upon startup of the signer, the signer is given a switch: `--seed ` +The `seed` contains a blob of bytes, which is the master seed for the `signer`. + +The `signer` uses the `seed` to: + +* Generate the `path` where the settings are stored. + * `./settings/1df094eb-c2b1-4689-90dd-790046d38025/vault.dat` + * `./settings/1df094eb-c2b1-4689-90dd-790046d38025/rules.js` +* Generate the encryption password for `vault.dat`. + +The `vault.dat` would be an encrypted container storing the following information: + +* `ksp` entries +* `sha256` hash of `rules.js` +* Information about pair:ed callers (not yet specified) + +### Security considerations + +This would leave it up to the user to ensure that the `path/to/masterseed` is handled in a secure way. It's difficult to get around this, although one could +imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`. + + +# Implementation status + +This is now implemented (with ephemeral non-encrypted storage for now, so not yet enabled). + +## Example 1: ruleset for a rate-limited window + + +```javascript + + function big(str){ + if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)} + return new BigNumber(str) + } + + // Time window: 1 week + var window = 1000* 3600*24*7; + + // Limit : 1 ether + var limit = new BigNumber("1e18"); + + function isLimitOk(transaction){ + var value = big(transaction.value) + // Start of our window function + var windowstart = new Date().getTime() - window; + + var txs = []; + var stored = storage.Get('txs'); + + if(stored != ""){ + txs = JSON.parse(stored) + } + // First, remove all that have passed out of the time-window + var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); + console.log(txs, newtxs.length); + + // Secondly, aggregate the current sum + sum = new BigNumber(0) + + sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); + console.log("ApproveTx > Sum so far", sum); + console.log("ApproveTx > Requested", value.toNumber()); + + // Would we exceed weekly limit ? + return sum.plus(value).lt(limit) + + } + function ApproveTx(r){ + if (isLimitOk(r.transaction)){ + return "Approve" + } + return "Nope" + } + + /** + * OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter + * 'response_str' contains the return value that will be sent to the external caller. + * The return value from this method is ignore - the reason for having this callback is to allow the + * ruleset to keep track of approved transactions. + * + * When implementing rate-limited rules, this callback should be used. + * If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user + * then accepts the transaction, this method will be called. + * + * TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx. + */ + function OnApprovedTx(resp){ + var value = big(resp.tx.value) + var txs = [] + // Load stored transactions + var stored = storage.Get('txs'); + if(stored != ""){ + txs = JSON.parse(stored) + } + // Add this to the storage + txs.push({tstamp: new Date().getTime(), value: value}); + storage.Put("txs", JSON.stringify(txs)); + } + +``` + +## Example 2: allow destination + +```javascript + + function ApproveTx(r){ + if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} + if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} + // Otherwise goes to manual processing + } + +``` + +## Example 3: Allow listing + +```javascript + + function ApproveListing(){ + return "Approve" + } + +``` \ No newline at end of file diff --git a/cmd/clef/sign_flow.png b/cmd/clef/sign_flow.png new file mode 100644 index 0000000000..9c0f3cc5d5 Binary files /dev/null and b/cmd/clef/sign_flow.png differ diff --git a/cmd/clef/tutorial.md b/cmd/clef/tutorial.md new file mode 100644 index 0000000000..d59e08ac7a --- /dev/null +++ b/cmd/clef/tutorial.md @@ -0,0 +1,198 @@ +## Initializing the signer + +First, initialize the master seed. + +```text +#./signer init + +WARNING! + +The signer is alpha software, and not yet publically released. This software has _not_ been audited, and there +are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software +unless you agree to take full responsibility for doing so, and know what you are doing. + +TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE! + + +Enter 'ok' to proceed: +>ok +A master seed has been generated into /home/martin/.signer/secrets.dat + +This is required to be able to store credentials, such as : +* Passwords for keystores (used by rule engine) +* Storage for javascript rules +* Hash of rule-file + +You should treat that file with utmost secrecy, and make a backup of it. +NOTE: This file does not contain your accounts. Those need to be backed up separately! +``` + +(for readability purposes, we'll remove the WARNING printout in the rest of this document) + +## Creating rules + +Now, you can create a rule-file. + +```javascript +function ApproveListing(){ + return "Approve" +} +``` +Get the `sha256` hash.... +```text +#sha256sum rules.js +6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72 rules.js +``` +...And then `attest` the file: +```text +#./signer attest 6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72 + +INFO [02-21|12:14:38] Ruleset attestation updated sha256=6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72 +``` +At this point, we then start the signer with the rule-file: + +```text +#./signer --rules rules.json + +INFO [02-21|12:15:18] Using CLI as UI-channel +INFO [02-21|12:15:18] Loaded 4byte db signatures=5509 file=./4byte.json +INFO [02-21|12:15:18] Could not load rulefile, rules not enabled file=rulefile +DEBUG[02-21|12:15:18] FS scan times list=35.335µs set=5.536µs diff=5.073µs +DEBUG[02-21|12:15:18] Ledger support enabled +DEBUG[02-21|12:15:18] Trezor support enabled +INFO [02-21|12:15:18] Audit logs configured file=audit.log +INFO [02-21|12:15:18] HTTP endpoint opened url=http://localhost:8550 +------- Signer info ------- +* extapi_http : http://localhost:8550 +* extapi_ipc : +* extapi_version : 2.0.0 +* intapi_version : 1.2.0 + +``` + +Any list-requests will now be auto-approved by our rule-file. + +## Under the hood + +While doing the operations above, these files have been created: + +```text +#ls -laR ~/.signer/ +/home/martin/.signer/: +total 16 +drwx------ 3 martin martin 4096 feb 21 12:14 . +drwxr-xr-x 71 martin martin 4096 feb 21 12:12 .. +drwx------ 2 martin martin 4096 feb 21 12:14 43f73718397aa54d1b22 +-rwx------ 1 martin martin 256 feb 21 12:12 secrets.dat + +/home/martin/.signer/43f73718397aa54d1b22: +total 12 +drwx------ 2 martin martin 4096 feb 21 12:14 . +drwx------ 3 martin martin 4096 feb 21 12:14 .. +-rw------- 1 martin martin 159 feb 21 12:14 config.json + +#cat /home/martin/.signer/43f73718397aa54d1b22/config.json +{"ruleset_sha256":{"iv":"6v4W4tfJxj3zZFbl","c":"6dt5RTDiTq93yh1qDEjpsat/tsKG7cb+vr3sza26IPL2fvsQ6ZoqFx++CPUa8yy6fD9Bbq41L01ehkKHTG3pOAeqTW6zc/+t0wv3AB6xPmU="}} + +``` + +In `~/.signer`, the `secrets.dat` file was created, containing the `master_seed`. +The `master_seed` was then used to derive a few other things: + +- `vault_location` : in this case `43f73718397aa54d1b22` . + - Thus, if you use a different `master_seed`, another `vault_location` will be used that does not conflict with each other. + - Example: `signer --signersecret /path/to/afile ...` +- `config.json` which is the encrypted key/value storage for configuration data, containing the key `ruleset_sha256`. + + +## Adding credentials + +In order to make more useful rules; sign transactions, the signer needs access to the passwords needed to unlock keystores. + +```text +#./signer addpw 0x694267f14675d7e1b9494fd8d72fefe1755710fa test + +INFO [02-21|13:43:21] Credential store updated key=0x694267f14675d7e1b9494fd8d72fefe1755710fa +``` +## More advanced rules + +Now let's update the rules to make use of credentials + +```javascript +function ApproveListing(){ + return "Approve" +} +function ApproveSignData(r){ + if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa") + { + if(r.message.indexOf("bazonk") >= 0){ + return "Approve" + } + return "Reject" + } + // Otherwise goes to manual processing +} + +``` +In this example, +* any requests to sign data with the account `0x694...` will be + * auto-approved if the message contains with `bazonk`, + * and auto-rejected if it does not. + * Any other signing-requests will be passed along for manual approve/reject. + +..attest the new file +```text +#sha256sum rules.js +2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f rules.js + +#./signer attest 2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f + +INFO [02-21|14:36:30] Ruleset attestation updated sha256=2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f +``` + +And start the signer: + +``` +#./signer --rules rules.js + +INFO [02-21|14:41:56] Using CLI as UI-channel +INFO [02-21|14:41:56] Loaded 4byte db signatures=5509 file=./4byte.json +INFO [02-21|14:41:56] Rule engine configured file=rules.js +DEBUG[02-21|14:41:56] FS scan times list=34.607µs set=4.509µs diff=4.87µs +DEBUG[02-21|14:41:56] Ledger support enabled +DEBUG[02-21|14:41:56] Trezor support enabled +INFO [02-21|14:41:56] Audit logs configured file=audit.log +INFO [02-21|14:41:56] HTTP endpoint opened url=http://localhost:8550 +------- Signer info ------- +* extapi_version : 2.0.0 +* intapi_version : 1.2.0 +* extapi_http : http://localhost:8550 +* extapi_ipc : +INFO [02-21|14:41:56] error occurred during execution error="ReferenceError: 'OnSignerStartup' is not defined" +``` +And then test signing, once with `bazonk` and once without: + +``` +#curl -H "Content-Type: application/json" -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"account_sign\",\"params\":[\"0x694267f14675d7e1b9494fd8d72fefe1755710fa\",\"0x$(xxd -pu <<< ' bazonk baz gaz')\"],\"id\":67}" http://localhost:8550/ +{"jsonrpc":"2.0","id":67,"result":"0x93e6161840c3ae1efc26dc68dedab6e8fc233bb3fefa1b4645dbf6609b93dace160572ea4ab33240256bb6d3dadb60dcd9c515d6374d3cf614ee897408d41d541c"} + +#curl -H "Content-Type: application/json" -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"account_sign\",\"params\":[\"0x694267f14675d7e1b9494fd8d72fefe1755710fa\",\"0x$(xxd -pu <<< ' bonk baz gaz')\"],\"id\":67}" http://localhost:8550/ +{"jsonrpc":"2.0","id":67,"error":{"code":-32000,"message":"Request denied"}} + +``` + +Meanwhile, in the signer output: +```text +INFO [02-21|14:42:41] Op approved +INFO [02-21|14:42:56] Op rejected +``` + +The signer also stores all traffic over the external API in a log file. The last 4 lines shows the two requests and their responses: + +```text +#tail audit.log -n 4 +t=2018-02-21T14:42:41+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49706\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=202062617a6f6e6b2062617a2067617a0a +t=2018-02-21T14:42:42+0100 lvl=info msg=Sign api=signer type=response data=93e6161840c3ae1efc26dc68dedab6e8fc233bb3fefa1b4645dbf6609b93dace160572ea4ab33240256bb6d3dadb60dcd9c515d6374d3cf614ee897408d41d541c error=nil +t=2018-02-21T14:42:56+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49708\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=2020626f6e6b2062617a2067617a0a +t=2018-02-21T14:42:56+0100 lvl=info msg=Sign api=signer type=response data= error="Request denied" +``` diff --git a/cmd/ethkey/main.go b/cmd/ethkey/main.go index 2a9e5ee483..4127f5566f 100644 --- a/cmd/ethkey/main.go +++ b/cmd/ethkey/main.go @@ -53,10 +53,6 @@ var ( Name: "json", Usage: "output JSON instead of human-readable format", } - messageFlag = cli.StringFlag{ - Name: "message", - Usage: "the file that contains the message to sign/verify", - } ) func main() { diff --git a/cmd/evm/json_logger.go b/cmd/evm/json_logger.go index 0e7a911896..f16424fbe6 100644 --- a/cmd/evm/json_logger.go +++ b/cmd/evm/json_logger.go @@ -32,6 +32,8 @@ type JSONLogger struct { cfg *vm.LogConfig } +// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects +// into the provided stream. func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger { return &JSONLogger{json.NewEncoder(writer), cfg} } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index c13e9fb335..7138a9ddd4 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -21,12 +21,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/big" "os" + goruntime "runtime" "runtime/pprof" "time" - goruntime "runtime" - "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" @@ -84,8 +84,9 @@ func runCmd(ctx *cli.Context) error { debugLogger *vm.StructLogger statedb *state.StateDB chainConfig *params.ChainConfig - sender = common.StringToAddress("sender") - receiver = common.StringToAddress("receiver") + sender = common.BytesToAddress([]byte("sender")) + receiver = common.BytesToAddress([]byte("receiver")) + blockNumber uint64 ) if ctx.GlobalBool(MachineFlag.Name) { tracer = NewJSONLogger(logconfig, os.Stdout) @@ -97,13 +98,13 @@ func runCmd(ctx *cli.Context) error { } if ctx.GlobalString(GenesisFlag.Name) != "" { gen := readGenesis(ctx.GlobalString(GenesisFlag.Name)) - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() genesis := gen.ToBlock(db) statedb, _ = state.New(genesis.Root(), state.NewDatabase(db)) chainConfig = gen.Config + blockNumber = gen.Number } else { - db, _ := ethdb.NewMemDatabase() - statedb, _ = state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) } if ctx.GlobalString(SenderFlag.Name) != "" { sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name)) @@ -156,11 +157,12 @@ func runCmd(ctx *cli.Context) error { initialGas := ctx.GlobalUint64(GasFlag.Name) runtimeConfig := runtime.Config{ - Origin: sender, - State: statedb, - GasLimit: initialGas, - GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), - Value: utils.GlobalBig(ctx, ValueFlag.Name), + Origin: sender, + State: statedb, + GasLimit: initialGas, + GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), + Value: utils.GlobalBig(ctx, ValueFlag.Name), + BlockNumber: new(big.Int).SetUint64(blockNumber), EVMConfig: vm.Config{ Tracer: tracer, Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 071ea94ad0..6d5ff069f6 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -38,6 +38,8 @@ var stateTestCommand = cli.Command{ ArgsUsage: "", } +// StatetestResult contains the execution status after running a state test, any +// error that might have occurred and a dump of the final state if requested. type StatetestResult struct { Name string `json:"name"` Pass bool `json:"pass"` diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index 0db5c4ce0f..071be85397 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -340,7 +340,7 @@ func importWallet(ctx *cli.Context) error { if len(keyfile) == 0 { utils.Fatalf("keyfile must be given as argument") } - keyJson, err := ioutil.ReadFile(keyfile) + keyJSON, err := ioutil.ReadFile(keyfile) if err != nil { utils.Fatalf("Could not read wallet file: %v", err) } @@ -349,7 +349,7 @@ func importWallet(ctx *cli.Context) error { passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - acct, err := ks.ImportPreSaleKey(keyJson, passphrase) + acct, err := ks.ImportPreSaleKey(keyJSON, passphrase) if err != nil { utils.Fatalf("%v", err) } diff --git a/cmd/geth/bugcmd.go b/cmd/geth/bugcmd.go index 51187ac90e..7e9a8ccc70 100644 --- a/cmd/geth/bugcmd.go +++ b/cmd/geth/bugcmd.go @@ -41,7 +41,7 @@ var bugCommand = cli.Command{ Category: "MISCELLANEOUS COMMANDS", } -const issueUrl = "https://github.com/ethereum/go-ethereum/issues/new" +const issueURL = "https://github.com/ethereum/go-ethereum/issues/new" // reportBug reports a bug by opening a new URL to the go-ethereum GH issue // tracker and setting default values as the issue body. @@ -58,8 +58,8 @@ func reportBug(ctx *cli.Context) error { fmt.Fprintln(&buff, header) // open a new GH issue - if !browser.Open(issueUrl + "?body=" + url.QueryEscape(buff.String())) { - fmt.Printf("Please file a new issue at %s using this template:\n\n%s", issueUrl, buff.String()) + if !browser.Open(issueURL + "?body=" + url.QueryEscape(buff.String())) { + fmt.Printf("Please file a new issue at %s using this template:\n\n%s", issueURL, buff.String()) } return nil } diff --git a/cmd/geth/dao_test.go b/cmd/geth/dao_test.go index a8dbc51630..52983ff2af 100644 --- a/cmd/geth/dao_test.go +++ b/cmd/geth/dao_test.go @@ -24,7 +24,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" ) @@ -131,8 +131,8 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc if genesis != "" { genesisHash = daoGenesisHash } - config, err := core.GetChainConfig(db, genesisHash) - if err != nil { + config := rawdb.ReadChainConfig(db, genesisHash) + if config == nil { t.Errorf("test %d: failed to retrieve chain config: %v", test, err) return // we want to return here, the other checks can't make it past this point (nil panic). } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 89e6767810..d147cf8f78 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -147,7 +147,7 @@ func init() { // Initialize the CLI app and start Geth app.Action = geth app.HideVersion = true // we have a command to print the version - app.Copyright = "Copyright 2013-2017 The go-ethereum Authors" + app.Copyright = "Copyright 2013-2018 The go-ethereum Authors" app.Commands = []cli.Command{ // See chaincmd.go: initCommand, @@ -227,6 +227,8 @@ func geth(ctx *cli.Context) error { // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. func startNode(ctx *cli.Context, stack *node.Node) { + debug.Memsize.Add("node", stack) + // Start up the node itself utils.StartNode(stack) @@ -245,7 +247,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { stack.AccountManager().Subscribe(events) go func() { - // Create an chain state reader for self-derivation + // Create a chain state reader for self-derivation rpcClient, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to self: %v", err) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 83b596db54..34937948f1 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -33,7 +33,7 @@ import ( var AppHelpTemplate = `NAME: {{.App.Name}} - {{.App.Usage}} - Copyright 2013-2017 The go-ethereum Authors + Copyright 2013-2018 The go-ethereum Authors USAGE: {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go index 3832b247f8..4f9e88899a 100644 --- a/cmd/puppeth/module_dashboard.go +++ b/cmd/puppeth/module_dashboard.go @@ -683,7 +683,7 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network)) } -// dashboardInfos is returned from an dashboard status check to allow reporting +// dashboardInfos is returned from a dashboard status check to allow reporting // various configuration parameters. type dashboardInfos struct { host string diff --git a/cmd/puppeth/module_explorer.go b/cmd/puppeth/module_explorer.go index 427134153b..bb43e5fe4e 100644 --- a/cmd/puppeth/module_explorer.go +++ b/cmd/puppeth/module_explorer.go @@ -168,7 +168,7 @@ func (info *explorerInfos) Report() map[string]string { return report } -// checkExplorer does a health-check against an block explorer server to verify +// checkExplorer does a health-check against a block explorer server to verify // whether it's running, and if yes, whether it's responsive. func checkExplorer(client *sshClient, network string) (*explorerInfos, error) { // Inspect a possible block explorer container on the host diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go index 976bf04d00..8365bf47d0 100644 --- a/cmd/puppeth/module_faucet.go +++ b/cmd/puppeth/module_faucet.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// faucetDockerfile is the Dockerfile required to build an faucet container to +// faucetDockerfile is the Dockerfile required to build a faucet container to // grant crypto tokens based on GitHub authentications. var faucetDockerfile = ` FROM ethereum/client-go:alltools-latest @@ -138,7 +138,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network)) } -// faucetInfos is returned from an faucet status check to allow reporting various +// faucetInfos is returned from a faucet status check to allow reporting various // configuration parameters. type faucetInfos struct { node *nodeInfos @@ -181,7 +181,7 @@ func (info *faucetInfos) Report() map[string]string { return report } -// checkFaucet does a health-check against an faucet server to verify whether +// checkFaucet does a health-check against a faucet server to verify whether // it's running, and if yes, gathering a collection of useful infos about it. func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { // Inspect a possible faucet container on the host diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go index 2609fd976e..1e1767c04b 100644 --- a/cmd/puppeth/module_node.go +++ b/cmd/puppeth/module_node.go @@ -198,7 +198,7 @@ func (info *nodeInfos) Report() map[string]string { return report } -// checkNode does a health-check against an boot or seal node server to verify +// checkNode does a health-check against a boot or seal node server to verify // whether it's running, and if yes, whether it's responsive. func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) { kind := "bootnode" diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index c0af4c13e7..58d72f32ba 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -271,15 +272,13 @@ func ImportPreimages(db *ethdb.LDBDatabase, fn string) error { // Accumulate the preimages and flush when enough ws gathered preimages[crypto.Keccak256Hash(blob)] = common.CopyBytes(blob) if len(preimages) > 1024 { - if err := core.WritePreimages(db, 0, preimages); err != nil { - return err - } + rawdb.WritePreimages(db, 0, preimages) preimages = make(map[common.Hash][]byte) } } // Flush the last batch preimage data if len(preimages) > 0 { - return core.WritePreimages(db, 0, preimages) + rawdb.WritePreimages(db, 0, preimages) } return nil } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a4eadcf094..8102cc8903 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -65,7 +65,7 @@ var ( {{if .cmd.Description}}{{.cmd.Description}} {{end}}{{if .cmd.Subcommands}} SUBCOMMANDS: - {{range .cmd.Subcommands}}{{.cmd.Name}}{{with .cmd.ShortName}}, {{.cmd}}{{end}}{{ "\t" }}{{.cmd.Usage}} + {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{end}}{{end}}{{if .categorizedFlags}} {{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS: {{range $categorized.Flags}}{{"\t"}}{{.}} @@ -159,11 +159,11 @@ var ( } FastSyncFlag = cli.BoolFlag{ Name: "fast", - Usage: "Enable fast syncing through state downloads", + Usage: "Enable fast syncing through state downloads (replaced by --syncmode)", } LightModeFlag = cli.BoolFlag{ Name: "light", - Usage: "Enable light client mode", + Usage: "Enable light client mode (replaced by --syncmode)", } defaultSyncMode = eth.DefaultConfig.SyncMode SyncModeFlag = TextMarshalerFlag{ diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 988c50ce3d..5031a088cb 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -271,7 +271,9 @@ func initialize() { if *mailServerMode { shh.RegisterServer(&mailServer) - mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW) + if err := mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW); err != nil { + utils.Fatalf("Failed to init MailServer: %s", err) + } } server = &p2p.Server{ diff --git a/common/bytes.go b/common/bytes.go index ba00e8a4b2..e2adc80059 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -87,15 +87,13 @@ func Hex2BytesFixed(str string, flen int) []byte { h, _ := hex.DecodeString(str) if len(h) == flen { return h - } else { - if len(h) > flen { - return h[len(h)-flen:] - } else { - hh := make([]byte, flen) - copy(hh[flen-len(h):flen], h[:]) - return hh - } } + if len(h) > flen { + return h[len(h)-flen:] + } + hh := make([]byte, flen) + copy(hh[flen-len(h):flen], h[:]) + return hh } func RightPadBytes(slice []byte, l int) []byte { diff --git a/common/types.go b/common/types.go index fdc67480c2..0b94fb2c25 100644 --- a/common/types.go +++ b/common/types.go @@ -18,10 +18,12 @@ package common import ( "encoding/hex" + "encoding/json" "fmt" "math/big" "math/rand" "reflect" + "strings" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto/sha3" @@ -45,9 +47,8 @@ func BytesToHash(b []byte) Hash { h.SetBytes(b) return h } -func StringToHash(s string) Hash { return BytesToHash([]byte(s)) } -func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } -func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } +func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } // Get the string representation of the underlying hash func (h Hash) Str() string { return string(h[:]) } @@ -143,9 +144,8 @@ func BytesToAddress(b []byte) Address { a.SetBytes(b) return a } -func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } -func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } -func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } +func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } +func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } // IsHexAddress verifies whether a string can represent a valid hex-encoded // Ethereum address or not. @@ -240,3 +240,63 @@ func (a *UnprefixedAddress) UnmarshalText(input []byte) error { func (a UnprefixedAddress) MarshalText() ([]byte, error) { return []byte(hex.EncodeToString(a[:])), nil } + +// MixedcaseAddress retains the original string, which may or may not be +// correctly checksummed +type MixedcaseAddress struct { + addr Address + original string +} + +// NewMixedcaseAddress constructor (mainly for testing) +func NewMixedcaseAddress(addr Address) MixedcaseAddress { + return MixedcaseAddress{addr: addr, original: addr.Hex()} +} + +// NewMixedcaseAddressFromString is mainly meant for unit-testing +func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) { + if !IsHexAddress(hexaddr) { + return nil, fmt.Errorf("Invalid address") + } + a := FromHex(hexaddr) + return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil +} + +// UnmarshalJSON parses MixedcaseAddress +func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error { + if err := hexutil.UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil { + return err + } + return json.Unmarshal(input, &ma.original) +} + +// MarshalJSON marshals the original value +func (ma *MixedcaseAddress) MarshalJSON() ([]byte, error) { + if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") { + return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:])) + } + return json.Marshal(fmt.Sprintf("0x%s", ma.original)) +} + +// Address returns the address +func (ma *MixedcaseAddress) Address() Address { + return ma.addr +} + +// String implements fmt.Stringer +func (ma *MixedcaseAddress) String() string { + if ma.ValidChecksum() { + return fmt.Sprintf("%s [chksum ok]", ma.original) + } + return fmt.Sprintf("%s [chksum INVALID]", ma.original) +} + +// ValidChecksum returns true if the address has valid checksum +func (ma *MixedcaseAddress) ValidChecksum() bool { + return ma.original == ma.addr.Hex() +} + +// Original returns the mixed-case input string +func (ma *MixedcaseAddress) Original() string { + return ma.original +} diff --git a/common/types_test.go b/common/types_test.go index db636812ce..9e0c5be3ad 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -18,6 +18,7 @@ package common import ( "encoding/json" + "math/big" "strings" "testing" @@ -149,3 +150,46 @@ func BenchmarkAddressHex(b *testing.B) { testAddr.Hex() } } + +func TestMixedcaseAccount_Address(t *testing.T) { + + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md + // Note: 0X{checksum_addr} is not valid according to spec above + + var res []struct { + A MixedcaseAddress + Valid bool + } + if err := json.Unmarshal([]byte(`[ + {"A" : "0xae967917c465db8578ca9024c205720b1a3651A9", "Valid": false}, + {"A" : "0xAe967917c465db8578ca9024c205720b1a3651A9", "Valid": true}, + {"A" : "0XAe967917c465db8578ca9024c205720b1a3651A9", "Valid": false}, + {"A" : "0x1111111111111111111112222222222223333323", "Valid": true} + ]`), &res); err != nil { + t.Fatal(err) + } + + for _, r := range res { + if got := r.A.ValidChecksum(); got != r.Valid { + t.Errorf("Expected checksum %v, got checksum %v, input %v", r.Valid, got, r.A.String()) + } + } + + //These should throw exceptions: + var r2 []MixedcaseAddress + for _, r := range []string{ + `["0x11111111111111111111122222222222233333"]`, // Too short + `["0x111111111111111111111222222222222333332"]`, // Too short + `["0x11111111111111111111122222222222233333234"]`, // Too long + `["0x111111111111111111111222222222222333332344"]`, // Too long + `["1111111111111111111112222222222223333323"]`, // Missing 0x + `["x1111111111111111111112222222222223333323"]`, // Missing 0 + `["0xG111111111111111111112222222222223333323"]`, //Non-hex + } { + if err := json.Unmarshal([]byte(r), &r2); err == nil { + t.Errorf("Expected failure, input %v", r) + } + + } + +} diff --git a/compression/rle/read_write.go b/compression/rle/read_write.go deleted file mode 100644 index 0e7ad90aec..0000000000 --- a/compression/rle/read_write.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2014 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 . - -// Package rle implements the run-length encoding used for Ethereum data. -package rle - -import ( - "bytes" - "errors" - - "github.com/ethereum/go-ethereum/crypto" -) - -const ( - token byte = 0xfe - emptyShaToken = 0xfd - emptyListShaToken = 0xfe - tokenToken = 0xff -) - -var empty = crypto.Keccak256([]byte("")) -var emptyList = crypto.Keccak256([]byte{0x80}) - -func Decompress(dat []byte) ([]byte, error) { - buf := new(bytes.Buffer) - - for i := 0; i < len(dat); i++ { - if dat[i] == token { - if i+1 < len(dat) { - switch dat[i+1] { - case emptyShaToken: - buf.Write(empty) - case emptyListShaToken: - buf.Write(emptyList) - case tokenToken: - buf.WriteByte(token) - default: - buf.Write(make([]byte, int(dat[i+1]-2))) - } - i++ - } else { - return nil, errors.New("error reading bytes. token encountered without proceeding bytes") - } - } else { - buf.WriteByte(dat[i]) - } - } - - return buf.Bytes(), nil -} - -func compressChunk(dat []byte) (ret []byte, n int) { - switch { - case dat[0] == token: - return []byte{token, tokenToken}, 1 - case len(dat) > 1 && dat[0] == 0x0 && dat[1] == 0x0: - j := 0 - for j <= 254 && j < len(dat) { - if dat[j] != 0 { - break - } - j++ - } - return []byte{token, byte(j + 2)}, j - case len(dat) >= 32: - if dat[0] == empty[0] && bytes.Equal(dat[:32], empty) { - return []byte{token, emptyShaToken}, 32 - } else if dat[0] == emptyList[0] && bytes.Equal(dat[:32], emptyList) { - return []byte{token, emptyListShaToken}, 32 - } - fallthrough - default: - return dat[:1], 1 - } -} - -func Compress(dat []byte) []byte { - buf := new(bytes.Buffer) - - i := 0 - for i < len(dat) { - b, n := compressChunk(dat[i:]) - buf.Write(b) - i += n - } - - return buf.Bytes() -} diff --git a/compression/rle/read_write_test.go b/compression/rle/read_write_test.go deleted file mode 100644 index b36f7907bc..0000000000 --- a/compression/rle/read_write_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 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 . - -package rle - -import ( - "testing" - - checker "gopkg.in/check.v1" -) - -func Test(t *testing.T) { checker.TestingT(t) } - -type CompressionRleSuite struct{} - -var _ = checker.Suite(&CompressionRleSuite{}) - -func (s *CompressionRleSuite) TestDecompressSimple(c *checker.C) { - exp := []byte{0xc5, 0xd2, 0x46, 0x1, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x3, 0xc0, 0xe5, 0x0, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x4, 0x5d, 0x85, 0xa4, 0x70} - res, err := Decompress([]byte{token, 0xfd}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, exp) - - exp = []byte{0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x1, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21} - res, err = Decompress([]byte{token, 0xfe}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, exp) - - res, err = Decompress([]byte{token, 0xff}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, []byte{token}) - - res, err = Decompress([]byte{token, 12}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, make([]byte, 10)) - -} diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index 8b51e6e094..29a8379832 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -81,7 +82,7 @@ func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") } func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header { if number == 0 { - return core.GetHeader(r.db, core.GetCanonicalHash(r.db, 0), 0) + return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0) } panic("not supported") } @@ -351,7 +352,7 @@ func TestVoting(t *testing.T) { copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) } // Create a pristine blockchain with the genesis injected - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() genesis.Commit(db) // Assemble a chain of headers from the cast votes diff --git a/consensus/consensus.go b/consensus/consensus.go index be5e661c12..5774af1a78 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -18,12 +18,13 @@ package consensus import ( + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "math/big" ) // ChainReader defines a small collection of methods needed to access the local diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 1b3dcee302..ac049f9c36 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -156,7 +156,7 @@ type lru struct { futureItem interface{} } -// newlru create a new least-recently-used cache for ither the verification caches +// newlru create a new least-recently-used cache for either the verification caches // or the mining datasets. func newlru(what string, maxItems int, new func(epoch uint64) interface{}) *lru { if maxItems <= 0 { diff --git a/console/bridge.go b/console/bridge.go index b28cc438e2..f2120351c0 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -87,7 +87,7 @@ func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) { // OpenWallet is a wrapper around personal.openWallet which can interpret and // react to certain error messages, such as the Trezor PIN matrix request. func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) { - // Make sure we have an wallet specified to open + // Make sure we have a wallet specified to open if !call.Argument(0).IsString() { throwJSException("first argument must be the wallet URL to open") } diff --git a/core/asm/compiler.go b/core/asm/compiler.go index 18dc0877ff..c273e7c51b 100644 --- a/core/asm/compiler.go +++ b/core/asm/compiler.go @@ -17,7 +17,6 @@ package asm import ( - "errors" "fmt" "math/big" "os" @@ -237,19 +236,16 @@ func (c *Compiler) pushBin(v interface{}) { // isPush returns whether the string op is either any of // push(N). func isPush(op string) bool { - return op == "push" + return strings.ToUpper(op) == "PUSH" } // isJump returns whether the string op is jump(i) func isJump(op string) bool { - return op == "jumpi" || op == "jump" + return strings.ToUpper(op) == "JUMPI" || strings.ToUpper(op) == "JUMP" } // toBinary converts text to a vm.OpCode func toBinary(text string) vm.OpCode { - if isPush(text) { - text = "push1" - } return vm.StringToOp(strings.ToUpper(text)) } @@ -264,11 +260,6 @@ func (err compileError) Error() string { return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) } -var ( - errExpBol = errors.New("expected beginning of line") - errExpElementOrLabel = errors.New("expected beginning of line") -) - func compileErr(c token, got, want string) error { return compileError{ got: got, diff --git a/core/bench_test.go b/core/bench_test.go index e23f0d19d1..748aebe407 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -148,7 +149,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Create the database in memory or in a temporary directory. var db ethdb.Database if !disk { - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() } else { dir, err := ioutil.TempDir("", "eth-core-bench") if err != nil { @@ -234,13 +235,15 @@ func makeChainForBench(db ethdb.Database, full bool, count uint64) { ReceiptHash: types.EmptyRootHash, } hash = header.Hash() - WriteHeader(db, header) - WriteCanonicalHash(db, hash, n) - WriteTd(db, hash, n, big.NewInt(int64(n+1))) + + rawdb.WriteHeader(db, header) + rawdb.WriteCanonicalHash(db, hash, n) + rawdb.WriteTd(db, hash, n, big.NewInt(int64(n+1))) + if full || n == 0 { block := types.NewBlockWithHeader(header) - WriteBody(db, hash, n, block.Body()) - WriteBlockReceipts(db, hash, n, nil) + rawdb.WriteBody(db, hash, n, block.Body()) + rawdb.WriteReceipts(db, hash, n, nil) } } } @@ -292,11 +295,10 @@ func benchReadChain(b *testing.B, full bool, count uint64) { header := chain.GetHeaderByNumber(n) if full { hash := header.Hash() - GetBody(db, hash, n) - GetBlockReceipts(db, hash, n) + rawdb.ReadBody(db, hash, n) + rawdb.ReadReceipts(db, hash, n) } } - chain.Stop() db.Close() } diff --git a/core/block_validator_test.go b/core/block_validator_test.go index e334b3c3cd..2a171218e3 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -32,7 +32,7 @@ import ( func TestHeaderVerification(t *testing.T) { // Create a simple chain to verify var ( - testdb, _ = ethdb.NewMemDatabase() + testdb = ethdb.NewMemDatabase() gspec = &Genesis{Config: params.TestChainConfig} genesis = gspec.MustCommit(testdb) blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil) @@ -84,7 +84,7 @@ func TestHeaderConcurrentVerification32(t *testing.T) { testHeaderConcurrentVeri func testHeaderConcurrentVerification(t *testing.T, threads int) { // Create a simple chain to verify var ( - testdb, _ = ethdb.NewMemDatabase() + testdb = ethdb.NewMemDatabase() gspec = &Genesis{Config: params.TestChainConfig} genesis = gspec.MustCommit(testdb) blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil) @@ -156,7 +156,7 @@ func TestHeaderConcurrentAbortion32(t *testing.T) { testHeaderConcurrentAbortion func testHeaderConcurrentAbortion(t *testing.T, threads int) { // Create a simple chain to verify var ( - testdb, _ = ethdb.NewMemDatabase() + testdb = ethdb.NewMemDatabase() gspec = &Genesis{Config: params.TestChainConfig} genesis = gspec.MustCommit(testdb) blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil) diff --git a/core/blockchain.go b/core/blockchain.go index b33eb85a44..f74a0f5b27 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -202,7 +203,7 @@ func (bc *BlockChain) getProcInterrupt() bool { // assumes that the chain manager mutex is held. func (bc *BlockChain) loadLastState() error { // Restore the last known head block - head := GetHeadBlockHash(bc.db) + head := rawdb.ReadHeadBlockHash(bc.db) if head == (common.Hash{}) { // Corrupt or empty database, init from scratch log.Warn("Empty database, resetting chain") @@ -228,7 +229,7 @@ func (bc *BlockChain) loadLastState() error { // Restore the last known head header currentHeader := currentBlock.Header() - if head := GetHeadHeaderHash(bc.db); head != (common.Hash{}) { + if head := rawdb.ReadHeadHeaderHash(bc.db); head != (common.Hash{}) { if header := bc.GetHeaderByHash(head); header != nil { currentHeader = header } @@ -237,7 +238,7 @@ func (bc *BlockChain) loadLastState() error { // Restore the last known head fast block bc.currentFastBlock.Store(currentBlock) - if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) { + if head := rawdb.ReadHeadFastBlockHash(bc.db); head != (common.Hash{}) { if block := bc.GetBlockByHash(head); block != nil { bc.currentFastBlock.Store(block) } @@ -269,7 +270,7 @@ func (bc *BlockChain) SetHead(head uint64) error { // Rewind the header chain, deleting all block bodies until then delFn := func(hash common.Hash, num uint64) { - DeleteBody(bc.db, hash, num) + rawdb.DeleteBody(bc.db, hash, num) } bc.hc.SetHead(head, delFn) currentHeader := bc.hc.CurrentHeader() @@ -303,12 +304,10 @@ func (bc *BlockChain) SetHead(head uint64) error { } currentBlock := bc.CurrentBlock() currentFastBlock := bc.CurrentFastBlock() - if err := WriteHeadBlockHash(bc.db, currentBlock.Hash()); err != nil { - log.Crit("Failed to reset head full block", "err", err) - } - if err := WriteHeadFastBlockHash(bc.db, currentFastBlock.Hash()); err != nil { - log.Crit("Failed to reset head fast block", "err", err) - } + + rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash()) + rawdb.WriteHeadFastBlockHash(bc.db, currentFastBlock.Hash()) + return bc.loadLastState() } @@ -406,9 +405,8 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { if err := bc.hc.WriteTd(genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil { log.Crit("Failed to write genesis block TD", "err", err) } - if err := WriteBlock(bc.db, genesis); err != nil { - log.Crit("Failed to write genesis block", "err", err) - } + rawdb.WriteBlock(bc.db, genesis) + bc.genesisBlock = genesis bc.insert(bc.genesisBlock) bc.currentBlock.Store(bc.genesisBlock) @@ -474,24 +472,19 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { // Note, this function assumes that the `mu` mutex is held! func (bc *BlockChain) insert(block *types.Block) { // If the block is on a side chain or an unknown one, force other heads onto it too - updateHeads := GetCanonicalHash(bc.db, block.NumberU64()) != block.Hash() + updateHeads := rawdb.ReadCanonicalHash(bc.db, block.NumberU64()) != block.Hash() // Add the block to the canonical chain number scheme and mark as the head - if err := WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64()); err != nil { - log.Crit("Failed to insert block number", "err", err) - } - if err := WriteHeadBlockHash(bc.db, block.Hash()); err != nil { - log.Crit("Failed to insert head block hash", "err", err) - } + rawdb.WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(bc.db, block.Hash()) + bc.currentBlock.Store(block) // If the block is better than our head or is on a different chain, force update heads if updateHeads { bc.hc.SetCurrentHeader(block.Header()) + rawdb.WriteHeadFastBlockHash(bc.db, block.Hash()) - if err := WriteHeadFastBlockHash(bc.db, block.Hash()); err != nil { - log.Crit("Failed to insert head fast block hash", "err", err) - } bc.currentFastBlock.Store(block) } } @@ -509,7 +502,11 @@ func (bc *BlockChain) GetBody(hash common.Hash) *types.Body { body := cached.(*types.Body) return body } - body := GetBody(bc.db, hash, bc.hc.GetBlockNumber(hash)) + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + body := rawdb.ReadBody(bc.db, hash, *number) if body == nil { return nil } @@ -525,7 +522,11 @@ func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue { if cached, ok := bc.bodyRLPCache.Get(hash); ok { return cached.(rlp.RawValue) } - body := GetBodyRLP(bc.db, hash, bc.hc.GetBlockNumber(hash)) + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + body := rawdb.ReadBodyRLP(bc.db, hash, *number) if len(body) == 0 { return nil } @@ -539,8 +540,7 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { if bc.blockCache.Contains(hash) { return true } - ok, _ := bc.db.Has(blockBodyKey(hash, number)) - return ok + return rawdb.HasBody(bc.db, hash, number) } // HasState checks if state trie is fully present in the database or not. @@ -567,7 +567,7 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { if block, ok := bc.blockCache.Get(hash); ok { return block.(*types.Block) } - block := GetBlock(bc.db, hash, number) + block := rawdb.ReadBlock(bc.db, hash, number) if block == nil { return nil } @@ -578,13 +578,17 @@ func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { // GetBlockByHash retrieves a block from the database by hash, caching it if found. func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block { - return bc.GetBlock(hash, bc.hc.GetBlockNumber(hash)) + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } + return bc.GetBlock(hash, *number) } // GetBlockByNumber retrieves a block from the database by number, caching it // (associated with its hash) if found. func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block { - hash := GetCanonicalHash(bc.db, number) + hash := rawdb.ReadCanonicalHash(bc.db, number) if hash == (common.Hash{}) { return nil } @@ -593,21 +597,28 @@ func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block { // GetReceiptsByHash retrieves the receipts for all transactions in a given block. func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts { - return GetBlockReceipts(bc.db, hash, GetBlockNumber(bc.db, hash)) + number := rawdb.ReadHeaderNumber(bc.db, hash) + if number == nil { + return nil + } + return rawdb.ReadReceipts(bc.db, hash, *number) } // GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors. // [deprecated by eth/62] func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) { number := bc.hc.GetBlockNumber(hash) + if number == nil { + return nil + } for i := 0; i < n; i++ { - block := bc.GetBlock(hash, number) + block := bc.GetBlock(hash, *number) if block == nil { break } blocks = append(blocks, block) hash = block.ParentHash() - number-- + *number-- } return } @@ -712,12 +723,12 @@ func (bc *BlockChain) Rollback(chain []common.Hash) { if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock.Hash() == hash { newFastBlock := bc.GetBlock(currentFastBlock.ParentHash(), currentFastBlock.NumberU64()-1) bc.currentFastBlock.Store(newFastBlock) - WriteHeadFastBlockHash(bc.db, newFastBlock.Hash()) + rawdb.WriteHeadFastBlockHash(bc.db, newFastBlock.Hash()) } if currentBlock := bc.CurrentBlock(); currentBlock.Hash() == hash { newBlock := bc.GetBlock(currentBlock.ParentHash(), currentBlock.NumberU64()-1) bc.currentBlock.Store(newBlock) - WriteHeadBlockHash(bc.db, newBlock.Hash()) + rawdb.WriteHeadBlockHash(bc.db, newBlock.Hash()) } } } @@ -802,15 +813,10 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ return i, fmt.Errorf("failed to set receipts data: %v", err) } // Write all the data out into the database - if err := WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()); err != nil { - return i, fmt.Errorf("failed to write block body: %v", err) - } - if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil { - return i, fmt.Errorf("failed to write block receipts: %v", err) - } - if err := WriteTxLookupEntries(batch, block); err != nil { - return i, fmt.Errorf("failed to write lookup metadata: %v", err) - } + rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()) + rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) + rawdb.WriteTxLookupEntries(batch, block) + stats.processed++ if batch.ValueSize() >= ethdb.IdealBatchSize { @@ -834,9 +840,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if td := bc.GetTd(head.Hash(), head.NumberU64()); td != nil { // Rewind may have occurred, skip in that case currentFastBlock := bc.CurrentFastBlock() if bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64()).Cmp(td) < 0 { - if err := WriteHeadFastBlockHash(bc.db, head.Hash()); err != nil { - log.Crit("Failed to update head fast block hash", "err", err) - } + rawdb.WriteHeadFastBlockHash(bc.db, head.Hash()) bc.currentFastBlock.Store(head) } } @@ -864,9 +868,8 @@ func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (e if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), td); err != nil { return err } - if err := WriteBlock(bc.db, block); err != nil { - return err - } + rawdb.WriteBlock(bc.db, block) + return nil } @@ -894,9 +897,8 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. } // Write other block data using a batch. batch := bc.db.NewBatch() - if err := WriteBlock(batch, block); err != nil { - return NonStatTy, err - } + rawdb.WriteBlock(batch, block) + root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) if err != nil { return NonStatTy, err @@ -953,9 +955,8 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. } } } - if err := WriteBlockReceipts(batch, block.Hash(), block.NumberU64(), receipts); err != nil { - return NonStatTy, err - } + rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) + // If the total difficulty is higher than our known, add it to the canonical chain // Second clause in the if statement reduces the vulnerability to selfish mining. // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf @@ -972,14 +973,10 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. return NonStatTy, err } } - // Write the positional metadata for transaction and receipt lookups - if err := WriteTxLookupEntries(batch, block); err != nil { - return NonStatTy, err - } - // Write hash preimages - if err := WritePreimages(bc.db, block.NumberU64(), state.Preimages()); err != nil { - return NonStatTy, err - } + // Write the positional metadata for transaction/receipt lookups and preimages + rawdb.WriteTxLookupEntries(batch, block) + rawdb.WritePreimages(batch, block.NumberU64(), state.Preimages()) + status = CanonStatTy } else { status = SideStatTy @@ -1256,9 +1253,13 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // collectLogs collects the logs that were generated during the // processing of the block that corresponds with the given hash. // These logs are later announced as deleted. - collectLogs = func(h common.Hash) { + collectLogs = func(hash common.Hash) { // Coalesce logs and set 'Removed'. - receipts := GetBlockReceipts(bc.db, h, bc.hc.GetBlockNumber(h)) + number := bc.hc.GetBlockNumber(hash) + if number == nil { + return + } + receipts := rawdb.ReadReceipts(bc.db, hash, *number) for _, receipt := range receipts { for _, log := range receipt.Logs { del := *log @@ -1327,9 +1328,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // insert the block in the canonical way, re-writing history bc.insert(newChain[i]) // write lookup entries for hash based transaction/receipt searches - if err := WriteTxLookupEntries(bc.db, newChain[i]); err != nil { - return err - } + rawdb.WriteTxLookupEntries(bc.db, newChain[i]) addedTxs = append(addedTxs, newChain[i].Transactions()...) } // calculate the difference between deleted and added transactions @@ -1337,7 +1336,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // When transactions get deleted from the database that means the // receipts that were created in the fork must also be deleted for _, tx := range diff { - DeleteTxLookupEntry(bc.db, tx.Hash()) + rawdb.DeleteTxLookupEntry(bc.db, tx.Hash()) } if len(deletedLogs) > 0 { go bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs}) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 748cdc5c71..89c071174f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -128,8 +129,8 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { return err } blockchain.mu.Lock() - WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash()))) - WriteBlock(blockchain.db, block) + rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash()))) + rawdb.WriteBlock(blockchain.db, block) statedb.Commit(false) blockchain.mu.Unlock() } @@ -146,8 +147,8 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error } // Manually insert the header into the database, but don't reorganise (allows subsequent testing) blockchain.mu.Lock() - WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash))) - WriteHeader(blockchain.db, header) + rawdb.WriteTd(blockchain.db, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash))) + rawdb.WriteHeader(blockchain.db, header) blockchain.mu.Unlock() } return nil @@ -173,7 +174,7 @@ func TestLastBlock(t *testing.T) { if _, err := blockchain.InsertChain(blocks); err != nil { t.Fatalf("Failed to insert block: %v", err) } - if blocks[len(blocks)-1].Hash() != GetHeadBlockHash(blockchain.db) { + if blocks[len(blocks)-1].Hash() != rawdb.ReadHeadBlockHash(blockchain.db) { t.Fatalf("Write/Get HeadBlockHash failed") } } @@ -568,11 +569,11 @@ func testInsertNonceError(t *testing.T, full bool) { func TestFastVsFullChains(t *testing.T) { // Configure and generate a sample block chain var ( - gendb, _ = ethdb.NewMemDatabase() - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(1000000000) - gspec = &Genesis{ + gendb = ethdb.NewMemDatabase() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}, } @@ -598,7 +599,7 @@ func TestFastVsFullChains(t *testing.T) { } }) // Import the chain as an archive node for the comparison baseline - archiveDb, _ := ethdb.NewMemDatabase() + archiveDb := ethdb.NewMemDatabase() gspec.MustCommit(archiveDb) archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) defer archive.Stop() @@ -607,7 +608,7 @@ func TestFastVsFullChains(t *testing.T) { t.Fatalf("failed to process block %d: %v", n, err) } // Fast import the chain as a non-archive node to test - fastDb, _ := ethdb.NewMemDatabase() + fastDb := ethdb.NewMemDatabase() gspec.MustCommit(fastDb) fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) defer fast.Stop() @@ -639,13 +640,13 @@ func TestFastVsFullChains(t *testing.T) { } else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(ablock.Uncles()) { t.Errorf("block #%d [%x]: uncles mismatch: have %v, want %v", num, hash, fblock.Uncles(), ablock.Uncles()) } - if freceipts, areceipts := GetBlockReceipts(fastDb, hash, GetBlockNumber(fastDb, hash)), GetBlockReceipts(archiveDb, hash, GetBlockNumber(archiveDb, hash)); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) { + if freceipts, areceipts := rawdb.ReadReceipts(fastDb, hash, *rawdb.ReadHeaderNumber(fastDb, hash)), rawdb.ReadReceipts(archiveDb, hash, *rawdb.ReadHeaderNumber(archiveDb, hash)); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) { t.Errorf("block #%d [%x]: receipts mismatch: have %v, want %v", num, hash, freceipts, areceipts) } } // Check that the canonical chains are the same between the databases for i := 0; i < len(blocks)+1; i++ { - if fhash, ahash := GetCanonicalHash(fastDb, uint64(i)), GetCanonicalHash(archiveDb, uint64(i)); fhash != ahash { + if fhash, ahash := rawdb.ReadCanonicalHash(fastDb, uint64(i)), rawdb.ReadCanonicalHash(archiveDb, uint64(i)); fhash != ahash { t.Errorf("block #%d: canonical hash mismatch: have %v, want %v", i, fhash, ahash) } } @@ -656,12 +657,12 @@ func TestFastVsFullChains(t *testing.T) { func TestLightVsFastVsFullChainHeads(t *testing.T) { // Configure and generate a sample block chain var ( - gendb, _ = ethdb.NewMemDatabase() - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(1000000000) - gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} - genesis = gspec.MustCommit(gendb) + gendb = ethdb.NewMemDatabase() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}} + genesis = gspec.MustCommit(gendb) ) height := uint64(1024) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), nil) @@ -684,7 +685,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { } } // Import the chain as an archive node and ensure all pointers are updated - archiveDb, _ := ethdb.NewMemDatabase() + archiveDb := ethdb.NewMemDatabase() gspec.MustCommit(archiveDb) archive, _ := NewBlockChain(archiveDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) @@ -698,7 +699,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { assert(t, "archive", archive, height/2, height/2, height/2) // Import the chain as a non-archive node and ensure all pointers are updated - fastDb, _ := ethdb.NewMemDatabase() + fastDb := ethdb.NewMemDatabase() gspec.MustCommit(fastDb) fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) defer fast.Stop() @@ -718,7 +719,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { assert(t, "fast", fast, height/2, height/2, 0) // Import the chain as a light node and ensure all pointers are updated - lightDb, _ := ethdb.NewMemDatabase() + lightDb := ethdb.NewMemDatabase() gspec.MustCommit(lightDb) light, _ := NewBlockChain(lightDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) @@ -741,7 +742,7 @@ func TestChainTxReorgs(t *testing.T) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr3 = crypto.PubkeyToAddress(key3.PublicKey) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() gspec = &Genesis{ Config: params.TestChainConfig, GasLimit: 3141592, @@ -821,28 +822,28 @@ func TestChainTxReorgs(t *testing.T) { // removed tx for i, tx := range (types.Transactions{pastDrop, freshDrop}) { - if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil { + if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn != nil { t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn) } - if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt != nil { + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt != nil { t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt) } } // added tx for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) { - if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil { + if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { t.Errorf("add %d: expected tx to be found", i) } - if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil { + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt == nil { t.Errorf("add %d: expected receipt to be found", i) } } // shared tx for i, tx := range (types.Transactions{postponed, swapped}) { - if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil { + if txn, _, _, _ := rawdb.ReadTransaction(db, tx.Hash()); txn == nil { t.Errorf("share %d: expected tx to be found", i) } - if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil { + if rcpt, _, _, _ := rawdb.ReadReceipt(db, tx.Hash()); rcpt == nil { t.Errorf("share %d: expected receipt to be found", i) } } @@ -853,7 +854,7 @@ func TestLogReorgs(t *testing.T) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() // this code generates a log code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00") gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}} @@ -897,7 +898,7 @@ func TestLogReorgs(t *testing.T) { func TestReorgSideEvent(t *testing.T) { var ( - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) gspec = &Genesis{ @@ -997,14 +998,14 @@ func TestCanonicalBlockRetrieval(t *testing.T) { // try to retrieve a block by its canonical hash and see if the block data can be retrieved. for { - ch := GetCanonicalHash(blockchain.db, block.NumberU64()) + ch := rawdb.ReadCanonicalHash(blockchain.db, block.NumberU64()) if ch == (common.Hash{}) { continue // busy wait for canonical hash to be written } if ch != block.Hash() { t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex()) } - fb := GetBlock(blockchain.db, ch, block.NumberU64()) + fb := rawdb.ReadBlock(blockchain.db, ch, block.NumberU64()) if fb == nil { t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex()) } @@ -1025,7 +1026,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) { func TestEIP155Transition(t *testing.T) { // Configure and generate a sample block chain var ( - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") address = crypto.PubkeyToAddress(key.PublicKey) funds = big.NewInt(1000000000) @@ -1129,7 +1130,7 @@ func TestEIP155Transition(t *testing.T) { func TestEIP161AccountRemoval(t *testing.T) { // Configure and generate a sample block chain var ( - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") address = crypto.PubkeyToAddress(key.PublicKey) funds = big.NewInt(1000000000) @@ -1201,7 +1202,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() genesis := new(Genesis).MustCommit(db) blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) @@ -1217,7 +1218,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { } // Import the canonical and fork chain side by side, verifying the current block // and current header consistency - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() new(Genesis).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) @@ -1246,7 +1247,7 @@ func TestTrieForkGC(t *testing.T) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() genesis := new(Genesis).MustCommit(db) blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) @@ -1261,7 +1262,7 @@ func TestTrieForkGC(t *testing.T) { forks[i] = fork[0] } // Import the canonical and fork chain side by side, forcing the trie cache to cache both - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() new(Genesis).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) @@ -1292,7 +1293,7 @@ func TestLargeReorgTrieGC(t *testing.T) { // Generate the original common chain segment and the two competing forks engine := ethash.NewFaker() - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() genesis := new(Genesis).MustCommit(db) shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) @@ -1300,7 +1301,7 @@ func TestLargeReorgTrieGC(t *testing.T) { competitor, _ := GenerateChain(params.TestChainConfig, shared[len(shared)-1], engine, db, 2*triesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) }) // Import the shared chain and the original canonical one - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() new(Genesis).MustCommit(diskdb) chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) @@ -1338,3 +1339,114 @@ func TestLargeReorgTrieGC(t *testing.T) { } } } + +// Benchmarks large blocks with value transfers to non-existing accounts +func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { + var ( + signer = types.HomesteadSigner{} + testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + bankFunds = big.NewInt(100000000000000000) + gspec = Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + testBankAddress: {Balance: bankFunds}, + common.HexToAddress("0xc0de"): { + Code: []byte{0x60, 0x01, 0x50}, + Balance: big.NewInt(0), + }, // push 1, pop + }, + GasLimit: 100e6, // 100 M + } + ) + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + db := ethdb.NewMemDatabase() + genesis := gspec.MustCommit(db) + + blockGenerator := func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{1}) + for txi := 0; txi < numTxs; txi++ { + uniq := uint64(i*numTxs + txi) + recipient := recipientFn(uniq) + //recipient := common.BigToAddress(big.NewInt(0).SetUint64(1337 + uniq)) + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey) + if err != nil { + b.Error(err) + } + block.AddTx(tx) + } + } + + shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, numBlocks, blockGenerator) + b.StopTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Import the shared chain and the original canonical one + diskdb := ethdb.NewMemDatabase() + gspec.MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) + if err != nil { + b.Fatalf("failed to create tester chain: %v", err) + } + b.StartTimer() + if _, err := chain.InsertChain(shared); err != nil { + b.Fatalf("failed to insert shared chain: %v", err) + } + b.StopTimer() + if got := chain.CurrentBlock().Transactions().Len(); got != numTxs*numBlocks { + b.Fatalf("Transactions were not included, expected %d, got %d", (numTxs * numBlocks), got) + + } + } +} +func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(1337 + nonce)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} +func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(1337)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} +func BenchmarkBlockChain_1x1000Executions(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(0xc0de)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go index ce3031702f..8d78adb759 100644 --- a/core/bloombits/matcher.go +++ b/core/bloombits/matcher.go @@ -392,7 +392,7 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests ) - // assign is a helper method fo try to assign a pending bit an an actively + // assign is a helper method fo try to assign a pending bit an actively // listening servicer, or schedule it up for later when one arrives. assign := func(bit uint) { select { diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 158ed83245..0b927116d0 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -24,6 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -206,7 +207,7 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainE // TODO(karalabe): This operation is expensive and might block, causing the event system to // potentially also lock up. We need to do with on a different thread somehow. - if h := FindCommonAncestor(c.chainDb, prevHeader, header); h != nil { + if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil { c.newHead(h.Number.Uint64(), true) } } @@ -349,11 +350,11 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com } for number := section * c.sectionSize; number < (section+1)*c.sectionSize; number++ { - hash := GetCanonicalHash(c.chainDb, number) + hash := rawdb.ReadCanonicalHash(c.chainDb, number) if hash == (common.Hash{}) { return common.Hash{}, fmt.Errorf("canonical block #%d unknown", number) } - header := GetHeader(c.chainDb, hash, number) + header := rawdb.ReadHeader(c.chainDb, hash, number) if header == nil { return common.Hash{}, fmt.Errorf("block #%d [%x…] not found", number, hash[:4]) } else if header.ParentHash != lastHead { diff --git a/core/chain_indexer_test.go b/core/chain_indexer_test.go index 9fc09eda51..550caf5567 100644 --- a/core/chain_indexer_test.go +++ b/core/chain_indexer_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" ) @@ -47,7 +48,7 @@ func TestChainIndexerWithChildren(t *testing.T) { // multiple backends. The section size and required confirmation count parameters // are randomized. func testChainIndexer(t *testing.T, count int) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() defer db.Close() // Create a chain of indexers and ensure they all report empty @@ -92,10 +93,10 @@ func testChainIndexer(t *testing.T, count int) { inject := func(number uint64) { header := &types.Header{Number: big.NewInt(int64(number)), Extra: big.NewInt(rand.Int63()).Bytes()} if number > 0 { - header.ParentHash = GetCanonicalHash(db, number-1) + header.ParentHash = rawdb.ReadCanonicalHash(db, number-1) } - WriteHeader(db, header) - WriteCanonicalHash(db, header.Hash(), number) + rawdb.WriteHeader(db, header) + rawdb.WriteCanonicalHash(db, header.Hash(), number) } // Start indexer with an already existing chain for i := uint64(0); i <= 100; i++ { diff --git a/core/chain_makers.go b/core/chain_makers.go index 31c9e3fb77..fcba90bb87 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -256,11 +256,12 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S // chain. Depending on the full flag, if creates either a full block chain or a // header only chain. func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) { - // Initialize a fresh chain with only a genesis block - gspec := new(Genesis) - db, _ := ethdb.NewMemDatabase() - genesis := gspec.MustCommit(db) + var ( + db = ethdb.NewMemDatabase() + genesis = new(Genesis).MustCommit(db) + ) + // Initialize a fresh chain with only a genesis block blockchain, _ := NewBlockChain(db, nil, params.AllEthashProtocolChanges, engine, vm.Config{}) // Create and inject the requested chain if n == 0 { diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 93be43ddce..5015d1f48e 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -36,7 +36,7 @@ func ExampleGenerateChain() { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr3 = crypto.PubkeyToAddress(key3.PublicKey) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() ) // Ensure that key1 has some funds in the genesis block. diff --git a/core/dao_test.go b/core/dao_test.go index e0a3e3ff37..284b1d98bd 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -32,13 +32,13 @@ func TestDAOForkRangeExtradata(t *testing.T) { forkBlock := big.NewInt(32) // Generate a common prefix for both pro-forkers and non-forkers - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() gspec := new(Genesis) genesis := gspec.MustCommit(db) prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {}) // Create the concurrent, conflicting two nodes - proDb, _ := ethdb.NewMemDatabase() + proDb := ethdb.NewMemDatabase() gspec.MustCommit(proDb) proConf := *params.TestChainConfig @@ -48,7 +48,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { proBc, _ := NewBlockChain(proDb, nil, &proConf, ethash.NewFaker(), vm.Config{}) defer proBc.Stop() - conDb, _ := ethdb.NewMemDatabase() + conDb := ethdb.NewMemDatabase() gspec.MustCommit(conDb) conConf := *params.TestChainConfig @@ -67,7 +67,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { // Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks for i := int64(0); i < params.DAOForkExtraRange.Int64(); i++ { // Create a pro-fork block, and try to feed into the no-fork chain - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() gspec.MustCommit(db) bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}) defer bc.Stop() @@ -92,7 +92,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err) } // Create a no-fork block, and try to feed into the pro-fork chain - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() gspec.MustCommit(db) bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}) defer bc.Stop() @@ -118,7 +118,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { } } // Verify that contra-forkers accept pro-fork extra-datas after forking finishes - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() gspec.MustCommit(db) bc, _ := NewBlockChain(db, nil, &conConf, ethash.NewFaker(), vm.Config{}) defer bc.Stop() @@ -138,7 +138,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err) } // Verify that pro-forkers accept contra-fork extra-datas after forking finishes - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() gspec.MustCommit(db) bc, _ = NewBlockChain(db, nil, &proConf, ethash.NewFaker(), vm.Config{}) defer bc.Stop() diff --git a/core/database_util.go b/core/database_util.go deleted file mode 100644 index 8c46989854..0000000000 --- a/core/database_util.go +++ /dev/null @@ -1,652 +0,0 @@ -// 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 . - -package core - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -// DatabaseReader wraps the Get method of a backing data store. -type DatabaseReader interface { - Get(key []byte) (value []byte, err error) -} - -// DatabaseDeleter wraps the Delete method of a backing data store. -type DatabaseDeleter interface { - Delete(key []byte) error -} - -var ( - headHeaderKey = []byte("LastHeader") - headBlockKey = []byte("LastBlock") - headFastKey = []byte("LastFast") - trieSyncKey = []byte("TrieSync") - - // Data item prefixes (use single byte to avoid mixing data types, avoid `i`). - headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header - tdSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td - numSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hash - blockHashPrefix = []byte("H") // blockHashPrefix + hash -> num (uint64 big endian) - bodyPrefix = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body - blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts - lookupPrefix = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata - bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits - - preimagePrefix = "secure-key-" // preimagePrefix + hash -> preimage - configPrefix = []byte("ethereum-config-") // config prefix for the db - - // Chain index prefixes (use `i` + single byte to avoid mixing data types). - BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress - - // used by old db, now only used for conversion - oldReceiptsPrefix = []byte("receipts-") - oldTxMetaSuffix = []byte{0x01} - - ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error - - preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) - preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) -) - -// TxLookupEntry is a positional metadata to help looking up the data content of -// a transaction or receipt given only its hash. -type TxLookupEntry struct { - BlockHash common.Hash - BlockIndex uint64 - Index uint64 -} - -// encodeBlockNumber encodes a block number as big endian uint64 -func encodeBlockNumber(number uint64) []byte { - enc := make([]byte, 8) - binary.BigEndian.PutUint64(enc, number) - return enc -} - -// GetCanonicalHash retrieves a hash assigned to a canonical block number. -func GetCanonicalHash(db DatabaseReader, number uint64) common.Hash { - data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...)) - if len(data) == 0 { - return common.Hash{} - } - return common.BytesToHash(data) -} - -// missingNumber is returned by GetBlockNumber if no header with the -// given block hash has been stored in the database -const missingNumber = uint64(0xffffffffffffffff) - -// GetBlockNumber returns the block number assigned to a block hash -// if the corresponding header is present in the database -func GetBlockNumber(db DatabaseReader, hash common.Hash) uint64 { - data, _ := db.Get(append(blockHashPrefix, hash.Bytes()...)) - if len(data) != 8 { - return missingNumber - } - return binary.BigEndian.Uint64(data) -} - -// GetHeadHeaderHash retrieves the hash of the current canonical head block's -// header. The difference between this and GetHeadBlockHash is that whereas the -// last block hash is only updated upon a full block import, the last header -// hash is updated already at header import, allowing head tracking for the -// light synchronization mechanism. -func GetHeadHeaderHash(db DatabaseReader) common.Hash { - data, _ := db.Get(headHeaderKey) - if len(data) == 0 { - return common.Hash{} - } - return common.BytesToHash(data) -} - -// GetHeadBlockHash retrieves the hash of the current canonical head block. -func GetHeadBlockHash(db DatabaseReader) common.Hash { - data, _ := db.Get(headBlockKey) - if len(data) == 0 { - return common.Hash{} - } - return common.BytesToHash(data) -} - -// GetHeadFastBlockHash retrieves the hash of the current canonical head block during -// fast synchronization. The difference between this and GetHeadBlockHash is that -// whereas the last block hash is only updated upon a full block import, the last -// fast hash is updated when importing pre-processed blocks. -func GetHeadFastBlockHash(db DatabaseReader) common.Hash { - data, _ := db.Get(headFastKey) - if len(data) == 0 { - return common.Hash{} - } - return common.BytesToHash(data) -} - -// GetTrieSyncProgress retrieves the number of tries nodes fast synced to allow -// reportinc correct numbers across restarts. -func GetTrieSyncProgress(db DatabaseReader) uint64 { - data, _ := db.Get(trieSyncKey) - if len(data) == 0 { - return 0 - } - return new(big.Int).SetBytes(data).Uint64() -} - -// GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil -// if the header's not found. -func GetHeaderRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue { - data, _ := db.Get(headerKey(hash, number)) - return data -} - -// GetHeader retrieves the block header corresponding to the hash, nil if none -// found. -func GetHeader(db DatabaseReader, hash common.Hash, number uint64) *types.Header { - data := GetHeaderRLP(db, hash, number) - if len(data) == 0 { - return nil - } - header := new(types.Header) - if err := rlp.Decode(bytes.NewReader(data), header); err != nil { - log.Error("Invalid block header RLP", "hash", hash, "err", err) - return nil - } - return header -} - -// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. -func GetBodyRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue { - data, _ := db.Get(blockBodyKey(hash, number)) - return data -} - -func headerKey(hash common.Hash, number uint64) []byte { - return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...) -} - -func blockBodyKey(hash common.Hash, number uint64) []byte { - return append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) -} - -// GetBody retrieves the block body (transactons, uncles) corresponding to the -// hash, nil if none found. -func GetBody(db DatabaseReader, hash common.Hash, number uint64) *types.Body { - data := GetBodyRLP(db, hash, number) - if len(data) == 0 { - return nil - } - body := new(types.Body) - if err := rlp.Decode(bytes.NewReader(data), body); err != nil { - log.Error("Invalid block body RLP", "hash", hash, "err", err) - return nil - } - return body -} - -// GetTd retrieves a block's total difficulty corresponding to the hash, nil if -// none found. -func GetTd(db DatabaseReader, hash common.Hash, number uint64) *big.Int { - data, _ := db.Get(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash[:]...), tdSuffix...)) - if len(data) == 0 { - return nil - } - td := new(big.Int) - if err := rlp.Decode(bytes.NewReader(data), td); err != nil { - log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err) - return nil - } - return td -} - -// GetBlock retrieves an entire block corresponding to the hash, assembling it -// back from the stored header and body. If either the header or body could not -// be retrieved nil is returned. -// -// Note, due to concurrent download of header and block body the header and thus -// canonical hash can be stored in the database but the body data not (yet). -func GetBlock(db DatabaseReader, hash common.Hash, number uint64) *types.Block { - // Retrieve the block header and body contents - header := GetHeader(db, hash, number) - if header == nil { - return nil - } - body := GetBody(db, hash, number) - if body == nil { - return nil - } - // Reassemble the block and return - return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles) -} - -// GetBlockReceipts retrieves the receipts generated by the transactions included -// in a block given by its hash. -func GetBlockReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts { - data, _ := db.Get(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash[:]...)) - if len(data) == 0 { - return nil - } - storageReceipts := []*types.ReceiptForStorage{} - if err := rlp.DecodeBytes(data, &storageReceipts); err != nil { - log.Error("Invalid receipt array RLP", "hash", hash, "err", err) - return nil - } - receipts := make(types.Receipts, len(storageReceipts)) - for i, receipt := range storageReceipts { - receipts[i] = (*types.Receipt)(receipt) - } - return receipts -} - -// GetTxLookupEntry retrieves the positional metadata associated with a transaction -// hash to allow retrieving the transaction or receipt by hash. -func GetTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64, uint64) { - // Load the positional metadata from disk and bail if it fails - data, _ := db.Get(append(lookupPrefix, hash.Bytes()...)) - if len(data) == 0 { - return common.Hash{}, 0, 0 - } - // Parse and return the contents of the lookup entry - var entry TxLookupEntry - if err := rlp.DecodeBytes(data, &entry); err != nil { - log.Error("Invalid lookup entry RLP", "hash", hash, "err", err) - return common.Hash{}, 0, 0 - } - return entry.BlockHash, entry.BlockIndex, entry.Index -} - -// GetTransaction retrieves a specific transaction from the database, along with -// its added positional metadata. -func GetTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { - // Retrieve the lookup metadata and resolve the transaction from the body - blockHash, blockNumber, txIndex := GetTxLookupEntry(db, hash) - - if blockHash != (common.Hash{}) { - body := GetBody(db, blockHash, blockNumber) - if body == nil || len(body.Transactions) <= int(txIndex) { - log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex) - return nil, common.Hash{}, 0, 0 - } - return body.Transactions[txIndex], blockHash, blockNumber, txIndex - } - // Old transaction representation, load the transaction and it's metadata separately - data, _ := db.Get(hash.Bytes()) - if len(data) == 0 { - return nil, common.Hash{}, 0, 0 - } - var tx types.Transaction - if err := rlp.DecodeBytes(data, &tx); err != nil { - return nil, common.Hash{}, 0, 0 - } - // Retrieve the blockchain positional metadata - data, _ = db.Get(append(hash.Bytes(), oldTxMetaSuffix...)) - if len(data) == 0 { - return nil, common.Hash{}, 0, 0 - } - var entry TxLookupEntry - if err := rlp.DecodeBytes(data, &entry); err != nil { - return nil, common.Hash{}, 0, 0 - } - return &tx, entry.BlockHash, entry.BlockIndex, entry.Index -} - -// GetReceipt retrieves a specific transaction receipt from the database, along with -// its added positional metadata. -func GetReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) { - // Retrieve the lookup metadata and resolve the receipt from the receipts - blockHash, blockNumber, receiptIndex := GetTxLookupEntry(db, hash) - - if blockHash != (common.Hash{}) { - receipts := GetBlockReceipts(db, blockHash, blockNumber) - if len(receipts) <= int(receiptIndex) { - log.Error("Receipt refereced missing", "number", blockNumber, "hash", blockHash, "index", receiptIndex) - return nil, common.Hash{}, 0, 0 - } - return receipts[receiptIndex], blockHash, blockNumber, receiptIndex - } - // Old receipt representation, load the receipt and set an unknown metadata - data, _ := db.Get(append(oldReceiptsPrefix, hash[:]...)) - if len(data) == 0 { - return nil, common.Hash{}, 0, 0 - } - var receipt types.ReceiptForStorage - err := rlp.DecodeBytes(data, &receipt) - if err != nil { - log.Error("Invalid receipt RLP", "hash", hash, "err", err) - } - return (*types.Receipt)(&receipt), common.Hash{}, 0, 0 -} - -// GetBloomBits retrieves the compressed bloom bit vector belonging to the given -// section and bit index from the. -func GetBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) ([]byte, error) { - key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...) - - binary.BigEndian.PutUint16(key[1:], uint16(bit)) - binary.BigEndian.PutUint64(key[3:], section) - - return db.Get(key) -} - -// WriteCanonicalHash stores the canonical hash for the given block number. -func WriteCanonicalHash(db ethdb.Putter, hash common.Hash, number uint64) error { - key := append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...) - if err := db.Put(key, hash.Bytes()); err != nil { - log.Crit("Failed to store number to hash mapping", "err", err) - } - return nil -} - -// WriteHeadHeaderHash stores the head header's hash. -func WriteHeadHeaderHash(db ethdb.Putter, hash common.Hash) error { - if err := db.Put(headHeaderKey, hash.Bytes()); err != nil { - log.Crit("Failed to store last header's hash", "err", err) - } - return nil -} - -// WriteHeadBlockHash stores the head block's hash. -func WriteHeadBlockHash(db ethdb.Putter, hash common.Hash) error { - if err := db.Put(headBlockKey, hash.Bytes()); err != nil { - log.Crit("Failed to store last block's hash", "err", err) - } - return nil -} - -// WriteHeadFastBlockHash stores the fast head block's hash. -func WriteHeadFastBlockHash(db ethdb.Putter, hash common.Hash) error { - if err := db.Put(headFastKey, hash.Bytes()); err != nil { - log.Crit("Failed to store last fast block's hash", "err", err) - } - return nil -} - -// WriteTrieSyncProgress stores the fast sync trie process counter to support -// retrieving it across restarts. -func WriteTrieSyncProgress(db ethdb.Putter, count uint64) error { - if err := db.Put(trieSyncKey, new(big.Int).SetUint64(count).Bytes()); err != nil { - log.Crit("Failed to store fast sync trie progress", "err", err) - } - return nil -} - -// WriteHeader serializes a block header into the database. -func WriteHeader(db ethdb.Putter, header *types.Header) error { - data, err := rlp.EncodeToBytes(header) - if err != nil { - return err - } - hash := header.Hash().Bytes() - num := header.Number.Uint64() - encNum := encodeBlockNumber(num) - key := append(blockHashPrefix, hash...) - if err := db.Put(key, encNum); err != nil { - log.Crit("Failed to store hash to number mapping", "err", err) - } - key = append(append(headerPrefix, encNum...), hash...) - if err := db.Put(key, data); err != nil { - log.Crit("Failed to store header", "err", err) - } - return nil -} - -// WriteBody serializes the body of a block into the database. -func WriteBody(db ethdb.Putter, hash common.Hash, number uint64, body *types.Body) error { - data, err := rlp.EncodeToBytes(body) - if err != nil { - return err - } - return WriteBodyRLP(db, hash, number, data) -} - -// WriteBodyRLP writes a serialized body of a block into the database. -func WriteBodyRLP(db ethdb.Putter, hash common.Hash, number uint64, rlp rlp.RawValue) error { - key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) - if err := db.Put(key, rlp); err != nil { - log.Crit("Failed to store block body", "err", err) - } - return nil -} - -// WriteTd serializes the total difficulty of a block into the database. -func WriteTd(db ethdb.Putter, hash common.Hash, number uint64, td *big.Int) error { - data, err := rlp.EncodeToBytes(td) - if err != nil { - return err - } - key := append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), tdSuffix...) - if err := db.Put(key, data); err != nil { - log.Crit("Failed to store block total difficulty", "err", err) - } - return nil -} - -// WriteBlock serializes a block into the database, header and body separately. -func WriteBlock(db ethdb.Putter, block *types.Block) error { - // Store the body first to retain database consistency - if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil { - return err - } - // Store the header too, signaling full block ownership - if err := WriteHeader(db, block.Header()); err != nil { - return err - } - return nil -} - -// WriteBlockReceipts stores all the transaction receipts belonging to a block -// as a single receipt slice. This is used during chain reorganisations for -// rescheduling dropped transactions. -func WriteBlockReceipts(db ethdb.Putter, hash common.Hash, number uint64, receipts types.Receipts) error { - // Convert the receipts into their storage form and serialize them - storageReceipts := make([]*types.ReceiptForStorage, len(receipts)) - for i, receipt := range receipts { - storageReceipts[i] = (*types.ReceiptForStorage)(receipt) - } - bytes, err := rlp.EncodeToBytes(storageReceipts) - if err != nil { - return err - } - // Store the flattened receipt slice - key := append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) - if err := db.Put(key, bytes); err != nil { - log.Crit("Failed to store block receipts", "err", err) - } - return nil -} - -// WriteTxLookupEntries stores a positional metadata for every transaction from -// a block, enabling hash based transaction and receipt lookups. -func WriteTxLookupEntries(db ethdb.Putter, block *types.Block) error { - // Iterate over each transaction and encode its metadata - for i, tx := range block.Transactions() { - entry := TxLookupEntry{ - BlockHash: block.Hash(), - BlockIndex: block.NumberU64(), - Index: uint64(i), - } - data, err := rlp.EncodeToBytes(entry) - if err != nil { - return err - } - if err := db.Put(append(lookupPrefix, tx.Hash().Bytes()...), data); err != nil { - return err - } - } - return nil -} - -// WriteBloomBits writes the compressed bloom bits vector belonging to the given -// section and bit index. -func WriteBloomBits(db ethdb.Putter, bit uint, section uint64, head common.Hash, bits []byte) { - key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...) - - binary.BigEndian.PutUint16(key[1:], uint16(bit)) - binary.BigEndian.PutUint64(key[3:], section) - - if err := db.Put(key, bits); err != nil { - log.Crit("Failed to store bloom bits", "err", err) - } -} - -// DeleteCanonicalHash removes the number to hash canonical mapping. -func DeleteCanonicalHash(db DatabaseDeleter, number uint64) { - db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), numSuffix...)) -} - -// DeleteHeader removes all block header data associated with a hash. -func DeleteHeader(db DatabaseDeleter, hash common.Hash, number uint64) { - db.Delete(append(blockHashPrefix, hash.Bytes()...)) - db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) -} - -// DeleteBody removes all block body data associated with a hash. -func DeleteBody(db DatabaseDeleter, hash common.Hash, number uint64) { - db.Delete(append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) -} - -// DeleteTd removes all block total difficulty data associated with a hash. -func DeleteTd(db DatabaseDeleter, hash common.Hash, number uint64) { - db.Delete(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), tdSuffix...)) -} - -// DeleteBlock removes all block data associated with a hash. -func DeleteBlock(db DatabaseDeleter, hash common.Hash, number uint64) { - DeleteBlockReceipts(db, hash, number) - DeleteHeader(db, hash, number) - DeleteBody(db, hash, number) - DeleteTd(db, hash, number) -} - -// DeleteBlockReceipts removes all receipt data associated with a block hash. -func DeleteBlockReceipts(db DatabaseDeleter, hash common.Hash, number uint64) { - db.Delete(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) -} - -// DeleteTxLookupEntry removes all transaction data associated with a hash. -func DeleteTxLookupEntry(db DatabaseDeleter, hash common.Hash) { - db.Delete(append(lookupPrefix, hash.Bytes()...)) -} - -// PreimageTable returns a Database instance with the key prefix for preimage entries. -func PreimageTable(db ethdb.Database) ethdb.Database { - return ethdb.NewTable(db, preimagePrefix) -} - -// WritePreimages writes the provided set of preimages to the database. `number` is the -// current block number, and is used for debug messages only. -func WritePreimages(db ethdb.Database, number uint64, preimages map[common.Hash][]byte) error { - table := PreimageTable(db) - batch := table.NewBatch() - hitCount := 0 - for hash, preimage := range preimages { - if _, err := table.Get(hash.Bytes()); err != nil { - batch.Put(hash.Bytes(), preimage) - hitCount++ - } - } - preimageCounter.Inc(int64(len(preimages))) - preimageHitCounter.Inc(int64(hitCount)) - if hitCount > 0 { - if err := batch.Write(); err != nil { - return fmt.Errorf("preimage write fail for block %d: %v", number, err) - } - } - return nil -} - -// GetBlockChainVersion reads the version number from db. -func GetBlockChainVersion(db DatabaseReader) int { - var vsn uint - enc, _ := db.Get([]byte("BlockchainVersion")) - rlp.DecodeBytes(enc, &vsn) - return int(vsn) -} - -// WriteBlockChainVersion writes vsn as the version number to db. -func WriteBlockChainVersion(db ethdb.Putter, vsn int) { - enc, _ := rlp.EncodeToBytes(uint(vsn)) - db.Put([]byte("BlockchainVersion"), enc) -} - -// WriteChainConfig writes the chain config settings to the database. -func WriteChainConfig(db ethdb.Putter, hash common.Hash, cfg *params.ChainConfig) error { - // short circuit and ignore if nil config. GetChainConfig - // will return a default. - if cfg == nil { - return nil - } - - jsonChainConfig, err := json.Marshal(cfg) - if err != nil { - return err - } - - return db.Put(append(configPrefix, hash[:]...), jsonChainConfig) -} - -// GetChainConfig will fetch the network settings based on the given hash. -func GetChainConfig(db DatabaseReader, hash common.Hash) (*params.ChainConfig, error) { - jsonChainConfig, _ := db.Get(append(configPrefix, hash[:]...)) - if len(jsonChainConfig) == 0 { - return nil, ErrChainConfigNotFound - } - - var config params.ChainConfig - if err := json.Unmarshal(jsonChainConfig, &config); err != nil { - return nil, err - } - - return &config, nil -} - -// FindCommonAncestor returns the last common ancestor of two block headers -func FindCommonAncestor(db DatabaseReader, a, b *types.Header) *types.Header { - for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { - a = GetHeader(db, a.ParentHash, a.Number.Uint64()-1) - if a == nil { - return nil - } - } - for an := a.Number.Uint64(); an < b.Number.Uint64(); { - b = GetHeader(db, b.ParentHash, b.Number.Uint64()-1) - if b == nil { - return nil - } - } - for a.Hash() != b.Hash() { - a = GetHeader(db, a.ParentHash, a.Number.Uint64()-1) - if a == nil { - return nil - } - b = GetHeader(db, b.ParentHash, b.Number.Uint64()-1) - if b == nil { - return nil - } - } - return a -} diff --git a/core/genesis.go b/core/genesis.go index b6ead2250a..9190e2ba22 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -155,7 +156,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig } // Just commit the new block if there is no stored genesis block. - stored := GetCanonicalHash(db, 0) + stored := rawdb.ReadCanonicalHash(db, 0) if (stored == common.Hash{}) { if genesis == nil { log.Info("Writing default main-net genesis block") @@ -177,14 +178,11 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) - storedcfg, err := GetChainConfig(db, stored) - if err != nil { - if err == ErrChainConfigNotFound { - // This case happens if a genesis write was interrupted. - log.Warn("Found genesis block without chain config") - err = WriteChainConfig(db, stored, newcfg) - } - return newcfg, stored, err + storedcfg := rawdb.ReadChainConfig(db, stored) + if storedcfg == nil { + log.Warn("Found genesis block without chain config") + rawdb.WriteChainConfig(db, stored, newcfg) + return newcfg, stored, nil } // Special case: don't change the existing config of a non-mainnet chain if no new // config is supplied. These chains would get AllProtocolChanges (and a compat error) @@ -195,15 +193,16 @@ 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 := GetBlockNumber(db, GetHeadHeaderHash(db)) - if height == missingNumber { + height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db)) + if height == nil { return newcfg, stored, fmt.Errorf("missing block number for head header hash") } - compatErr := storedcfg.CheckCompatible(newcfg, height) - if compatErr != nil && height != 0 && compatErr.RewindTo != 0 { + compatErr := storedcfg.CheckCompatible(newcfg, *height) + if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 { return newcfg, stored, compatErr } - return newcfg, stored, WriteChainConfig(db, stored, newcfg) + rawdb.WriteChainConfig(db, stored, newcfg) + return newcfg, stored, nil } func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { @@ -223,7 +222,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { // to the given database (or discards it if nil). func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { if db == nil { - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() } statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) for addr, account := range g.Alloc { @@ -267,29 +266,19 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { if block.Number().Sign() != 0 { return nil, fmt.Errorf("can't commit genesis block with number > 0") } - if err := WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty); err != nil { - return nil, err - } - if err := WriteBlock(db, block); err != nil { - return nil, err - } - if err := WriteBlockReceipts(db, block.Hash(), block.NumberU64(), nil); err != nil { - return nil, err - } - if err := WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil { - return nil, err - } - if err := WriteHeadBlockHash(db, block.Hash()); err != nil { - return nil, err - } - if err := WriteHeadHeaderHash(db, block.Hash()); err != nil { - return nil, err - } + rawdb.WriteTd(db, block.Hash(), block.NumberU64(), g.Difficulty) + rawdb.WriteBlock(db, block) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteHeadHeaderHash(db, block.Hash()) + config := g.Config if config == nil { config = params.AllEthashProtocolChanges } - return block, WriteChainConfig(db, block.Hash(), config) + rawdb.WriteChainConfig(db, block.Hash(), config) + return block, nil } // MustCommit writes the genesis block and state to db, panicking on error. diff --git a/core/genesis_test.go b/core/genesis_test.go index 052ded6991..2d7f94f8f8 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -24,6 +24,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" @@ -140,7 +141,7 @@ func TestSetupGenesis(t *testing.T) { } for _, test := range tests { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() config, hash, err := test.fn(db) // Check the return values. if !reflect.DeepEqual(err, test.wantErr) { @@ -154,7 +155,7 @@ func TestSetupGenesis(t *testing.T) { t.Errorf("%s: returned hash %s, want %s", test.name, hash.Hex(), test.wantHash.Hex()) } else if err == nil { // Check database content. - stored := GetBlock(db, test.wantHash, 0) + 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) } diff --git a/core/headerchain.go b/core/headerchain.go index 2d1b0a2a18..2ac0cccc72 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -97,7 +98,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c } hc.currentHeader.Store(hc.genesisHeader) - if head := GetHeadBlockHash(chainDb); head != (common.Hash{}) { + if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) { if chead := hc.GetHeaderByHash(head); chead != nil { hc.currentHeader.Store(chead) } @@ -109,13 +110,14 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c // GetBlockNumber retrieves the block number belonging to the given hash // from the cache or database -func (hc *HeaderChain) GetBlockNumber(hash common.Hash) uint64 { +func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 { if cached, ok := hc.numberCache.Get(hash); ok { - return cached.(uint64) + number := cached.(uint64) + return &number } - number := GetBlockNumber(hc.chainDb, hash) - if number != missingNumber { - hc.numberCache.Add(hash, number) + number := rawdb.ReadHeaderNumber(hc.chainDb, hash) + if number != nil { + hc.numberCache.Add(hash, *number) } return number } @@ -147,20 +149,19 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er if err := hc.WriteTd(hash, number, externTd); err != nil { log.Crit("Failed to write header total difficulty", "err", err) } - if err := WriteHeader(hc.chainDb, header); err != nil { - log.Crit("Failed to write header content", "err", err) - } + rawdb.WriteHeader(hc.chainDb, header) + // If the total difficulty is higher than our known, add it to the canonical chain // Second clause in the if statement reduces the vulnerability to selfish mining. // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) { // Delete any canonical number assignments above the new head for i := number + 1; ; i++ { - hash := GetCanonicalHash(hc.chainDb, i) + hash := rawdb.ReadCanonicalHash(hc.chainDb, i) if hash == (common.Hash{}) { break } - DeleteCanonicalHash(hc.chainDb, i) + rawdb.DeleteCanonicalHash(hc.chainDb, i) } // Overwrite any stale canonical number assignments var ( @@ -168,20 +169,17 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er headNumber = header.Number.Uint64() - 1 headHeader = hc.GetHeader(headHash, headNumber) ) - for GetCanonicalHash(hc.chainDb, headNumber) != headHash { - WriteCanonicalHash(hc.chainDb, headHash, headNumber) + for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { + rawdb.WriteCanonicalHash(hc.chainDb, headHash, headNumber) headHash = headHeader.ParentHash headNumber = headHeader.Number.Uint64() - 1 headHeader = hc.GetHeader(headHash, headNumber) } // Extend the canonical chain with the new header - if err := WriteCanonicalHash(hc.chainDb, hash, number); err != nil { - log.Crit("Failed to insert header number", "err", err) - } - if err := WriteHeadHeaderHash(hc.chainDb, hash); err != nil { - log.Crit("Failed to insert head header hash", "err", err) - } + rawdb.WriteCanonicalHash(hc.chainDb, hash, number) + rawdb.WriteHeadHeaderHash(hc.chainDb, hash) + hc.currentHeaderHash = hash hc.currentHeader.Store(types.CopyHeader(header)) @@ -316,7 +314,7 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int { if cached, ok := hc.tdCache.Get(hash); ok { return cached.(*big.Int) } - td := GetTd(hc.chainDb, hash, number) + td := rawdb.ReadTd(hc.chainDb, hash, number) if td == nil { return nil } @@ -328,15 +326,17 @@ func (hc *HeaderChain) GetTd(hash common.Hash, number uint64) *big.Int { // GetTdByHash retrieves a block's total difficulty in the canonical chain from the // database by hash, caching it if found. func (hc *HeaderChain) GetTdByHash(hash common.Hash) *big.Int { - return hc.GetTd(hash, hc.GetBlockNumber(hash)) + number := hc.GetBlockNumber(hash) + if number == nil { + return nil + } + return hc.GetTd(hash, *number) } // WriteTd stores a block's total difficulty into the database, also caching it // along the way. func (hc *HeaderChain) WriteTd(hash common.Hash, number uint64, td *big.Int) error { - if err := WriteTd(hc.chainDb, hash, number, td); err != nil { - return err - } + rawdb.WriteTd(hc.chainDb, hash, number, td) hc.tdCache.Add(hash, new(big.Int).Set(td)) return nil } @@ -348,7 +348,7 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header if header, ok := hc.headerCache.Get(hash); ok { return header.(*types.Header) } - header := GetHeader(hc.chainDb, hash, number) + header := rawdb.ReadHeader(hc.chainDb, hash, number) if header == nil { return nil } @@ -360,7 +360,11 @@ func (hc *HeaderChain) GetHeader(hash common.Hash, number uint64) *types.Header // GetHeaderByHash retrieves a block header from the database by hash, caching it if // found. func (hc *HeaderChain) GetHeaderByHash(hash common.Hash) *types.Header { - return hc.GetHeader(hash, hc.GetBlockNumber(hash)) + number := hc.GetBlockNumber(hash) + if number == nil { + return nil + } + return hc.GetHeader(hash, *number) } // HasHeader checks if a block header is present in the database or not. @@ -368,14 +372,13 @@ func (hc *HeaderChain) HasHeader(hash common.Hash, number uint64) bool { if hc.numberCache.Contains(hash) || hc.headerCache.Contains(hash) { return true } - ok, _ := hc.chainDb.Has(headerKey(hash, number)) - return ok + return rawdb.HasHeader(hc.chainDb, hash, number) } // GetHeaderByNumber retrieves a block header from the database by number, // caching it (associated with its hash) if found. func (hc *HeaderChain) GetHeaderByNumber(number uint64) *types.Header { - hash := GetCanonicalHash(hc.chainDb, number) + hash := rawdb.ReadCanonicalHash(hc.chainDb, number) if hash == (common.Hash{}) { return nil } @@ -390,9 +393,8 @@ func (hc *HeaderChain) CurrentHeader() *types.Header { // SetCurrentHeader sets the current head header of the canonical chain. func (hc *HeaderChain) SetCurrentHeader(head *types.Header) { - if err := WriteHeadHeaderHash(hc.chainDb, head.Hash()); err != nil { - log.Crit("Failed to insert head header hash", "err", err) - } + rawdb.WriteHeadHeaderHash(hc.chainDb, head.Hash()) + hc.currentHeader.Store(head) hc.currentHeaderHash = head.Hash() } @@ -416,13 +418,14 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) { if delFn != nil { delFn(hash, num) } - DeleteHeader(hc.chainDb, hash, num) - DeleteTd(hc.chainDb, hash, num) + rawdb.DeleteHeader(hc.chainDb, hash, num) + rawdb.DeleteTd(hc.chainDb, hash, num) + hc.currentHeader.Store(hc.GetHeader(hdr.ParentHash, hdr.Number.Uint64()-1)) } // Roll back the canonical chain numbering for i := height; i > head; i-- { - DeleteCanonicalHash(hc.chainDb, i) + rawdb.DeleteCanonicalHash(hc.chainDb, i) } // Clear out any stale content from the caches hc.headerCache.Purge() @@ -434,9 +437,7 @@ func (hc *HeaderChain) SetHead(head uint64, delFn DeleteCallback) { } hc.currentHeaderHash = hc.CurrentHeader().Hash() - if err := WriteHeadHeaderHash(hc.chainDb, hc.currentHeaderHash); err != nil { - log.Crit("Failed to reset head header hash", "err", err) - } + rawdb.WriteHeadHeaderHash(hc.chainDb, hc.currentHeaderHash) } // SetGenesis sets a new genesis block header for the chain diff --git a/core/helper_test.go b/core/helper_test.go index 698a2924cb..051384d854 100644 --- a/core/helper_test.go +++ b/core/helper_test.go @@ -18,7 +18,6 @@ package core import ( "container/list" - "fmt" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -77,18 +76,11 @@ func (tm *TestManager) Db() ethdb.Database { } func NewTestManager() *TestManager { - db, err := ethdb.NewMemDatabase() - if err != nil { - fmt.Println("Could not create mem-db, failing") - return nil - } - testManager := &TestManager{} testManager.eventMux = new(event.TypeMux) - testManager.db = db + testManager.db = ethdb.NewMemDatabase() // testManager.txPool = NewTxPool(testManager) // testManager.blockChain = NewBlockChain(testManager) // testManager.stateManager = NewStateManager(testManager) - return testManager } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go new file mode 100644 index 0000000000..a26a42ba7c --- /dev/null +++ b/core/rawdb/accessors_chain.go @@ -0,0 +1,381 @@ +// Copyright 2018 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 . + +package rawdb + +import ( + "bytes" + "encoding/binary" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// ReadCanonicalHash retrieves the hash assigned to a canonical block number. +func ReadCanonicalHash(db DatabaseReader, number uint64) common.Hash { + data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...)) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteCanonicalHash stores the hash assigned to a canonical block number. +func WriteCanonicalHash(db DatabaseWriter, hash common.Hash, number uint64) { + key := append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...) + if err := db.Put(key, hash.Bytes()); err != nil { + log.Crit("Failed to store number to hash mapping", "err", err) + } +} + +// DeleteCanonicalHash removes the number to hash canonical mapping. +func DeleteCanonicalHash(db DatabaseDeleter, number uint64) { + if err := db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...)); err != nil { + log.Crit("Failed to delete number to hash mapping", "err", err) + } +} + +// ReadHeaderNumber returns the header number assigned to a hash. +func ReadHeaderNumber(db DatabaseReader, hash common.Hash) *uint64 { + data, _ := db.Get(append(headerNumberPrefix, hash.Bytes()...)) + if len(data) != 8 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// ReadHeadHeaderHash retrieves the hash of the current canonical head header. +func ReadHeadHeaderHash(db DatabaseReader) common.Hash { + data, _ := db.Get(headHeaderKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteHeadHeaderHash stores the hash of the current canonical head header. +func WriteHeadHeaderHash(db DatabaseWriter, hash common.Hash) { + if err := db.Put(headHeaderKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last header's hash", "err", err) + } +} + +// ReadHeadBlockHash retrieves the hash of the current canonical head block. +func ReadHeadBlockHash(db DatabaseReader) common.Hash { + data, _ := db.Get(headBlockKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteHeadBlockHash stores the head block's hash. +func WriteHeadBlockHash(db DatabaseWriter, hash common.Hash) { + if err := db.Put(headBlockKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last block's hash", "err", err) + } +} + +// ReadHeadFastBlockHash retrieves the hash of the current fast-sync head block. +func ReadHeadFastBlockHash(db DatabaseReader) common.Hash { + data, _ := db.Get(headFastBlockKey) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// WriteHeadFastBlockHash stores the hash of the current fast-sync head block. +func WriteHeadFastBlockHash(db DatabaseWriter, hash common.Hash) { + if err := db.Put(headFastBlockKey, hash.Bytes()); err != nil { + log.Crit("Failed to store last fast block's hash", "err", err) + } +} + +// ReadFastTrieProgress retrieves the number of tries nodes fast synced to allow +// reporting correct numbers across restarts. +func ReadFastTrieProgress(db DatabaseReader) uint64 { + data, _ := db.Get(fastTrieProgressKey) + if len(data) == 0 { + return 0 + } + return new(big.Int).SetBytes(data).Uint64() +} + +// WriteFastTrieProgress stores the fast sync trie process counter to support +// retrieving it across restarts. +func WriteFastTrieProgress(db DatabaseWriter, count uint64) { + if err := db.Put(fastTrieProgressKey, new(big.Int).SetUint64(count).Bytes()); err != nil { + log.Crit("Failed to store fast sync trie progress", "err", err) + } +} + +// ReadHeaderRLP retrieves a block header in its raw RLP database encoding. +func ReadHeaderRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue { + data, _ := db.Get(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) + return data +} + +// HasHeader verifies the existence of a block header corresponding to the hash. +func HasHeader(db DatabaseReader, hash common.Hash, number uint64) bool { + key := append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) + if has, err := db.Has(key); !has || err != nil { + return false + } + return true +} + +// ReadHeader retrieves the block header corresponding to the hash. +func ReadHeader(db DatabaseReader, hash common.Hash, number uint64) *types.Header { + data := ReadHeaderRLP(db, hash, number) + if len(data) == 0 { + return nil + } + header := new(types.Header) + if err := rlp.Decode(bytes.NewReader(data), header); err != nil { + log.Error("Invalid block header RLP", "hash", hash, "err", err) + return nil + } + return header +} + +// WriteHeader stores a block header into the database and also stores the hash- +// to-number mapping. +func WriteHeader(db DatabaseWriter, header *types.Header) { + // Write the hash -> number mapping + var ( + hash = header.Hash().Bytes() + number = header.Number.Uint64() + encoded = encodeBlockNumber(number) + ) + key := append(headerNumberPrefix, hash...) + if err := db.Put(key, encoded); err != nil { + log.Crit("Failed to store hash to number mapping", "err", err) + } + // Write the encoded header + data, err := rlp.EncodeToBytes(header) + if err != nil { + log.Crit("Failed to RLP encode header", "err", err) + } + key = append(append(headerPrefix, encoded...), hash...) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store header", "err", err) + } +} + +// DeleteHeader removes all block header data associated with a hash. +func DeleteHeader(db DatabaseDeleter, hash common.Hash, number uint64) { + if err := db.Delete(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...)); err != nil { + log.Crit("Failed to delete header", "err", err) + } + if err := db.Delete(append(headerNumberPrefix, hash.Bytes()...)); err != nil { + log.Crit("Failed to delete hash to number mapping", "err", err) + } +} + +// ReadBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. +func ReadBodyRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue { + data, _ := db.Get(append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)) + return data +} + +// WriteBodyRLP stores an RLP encoded block body into the database. +func WriteBodyRLP(db DatabaseWriter, hash common.Hash, number uint64, rlp rlp.RawValue) { + key := append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) + if err := db.Put(key, rlp); err != nil { + log.Crit("Failed to store block body", "err", err) + } +} + +// HasBody verifies the existence of a block body corresponding to the hash. +func HasBody(db DatabaseReader, hash common.Hash, number uint64) bool { + key := append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) + if has, err := db.Has(key); !has || err != nil { + return false + } + return true +} + +// ReadBody retrieves the block body corresponding to the hash. +func ReadBody(db DatabaseReader, hash common.Hash, number uint64) *types.Body { + data := ReadBodyRLP(db, hash, number) + if len(data) == 0 { + return nil + } + body := new(types.Body) + if err := rlp.Decode(bytes.NewReader(data), body); err != nil { + log.Error("Invalid block body RLP", "hash", hash, "err", err) + return nil + } + return body +} + +// WriteBody storea a block body into the database. +func WriteBody(db DatabaseWriter, hash common.Hash, number uint64, body *types.Body) { + data, err := rlp.EncodeToBytes(body) + if err != nil { + log.Crit("Failed to RLP encode body", "err", err) + } + WriteBodyRLP(db, hash, number, data) +} + +// DeleteBody removes all block body data associated with a hash. +func DeleteBody(db DatabaseDeleter, hash common.Hash, number uint64) { + if err := db.Delete(append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)); err != nil { + log.Crit("Failed to delete block body", "err", err) + } +} + +// ReadTd retrieves a block's total difficulty corresponding to the hash. +func ReadTd(db DatabaseReader, hash common.Hash, number uint64) *big.Int { + data, _ := db.Get(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash[:]...), headerTDSuffix...)) + if len(data) == 0 { + return nil + } + td := new(big.Int) + if err := rlp.Decode(bytes.NewReader(data), td); err != nil { + log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err) + return nil + } + return td +} + +// WriteTd stores the total difficulty of a block into the database. +func WriteTd(db DatabaseWriter, hash common.Hash, number uint64, td *big.Int) { + data, err := rlp.EncodeToBytes(td) + if err != nil { + log.Crit("Failed to RLP encode block total difficulty", "err", err) + } + key := append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), headerTDSuffix...) + if err := db.Put(key, data); err != nil { + log.Crit("Failed to store block total difficulty", "err", err) + } +} + +// DeleteTd removes all block total difficulty data associated with a hash. +func DeleteTd(db DatabaseDeleter, hash common.Hash, number uint64) { + if err := db.Delete(append(append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...), headerTDSuffix...)); err != nil { + log.Crit("Failed to delete block total difficulty", "err", err) + } +} + +// ReadReceipts retrieves all the transaction receipts belonging to a block. +func ReadReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Receipts { + // Retrieve the flattened receipt slice + data, _ := db.Get(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash[:]...)) + if len(data) == 0 { + return nil + } + // Convert the revceipts from their storage form to their internal representation + storageReceipts := []*types.ReceiptForStorage{} + if err := rlp.DecodeBytes(data, &storageReceipts); err != nil { + log.Error("Invalid receipt array RLP", "hash", hash, "err", err) + return nil + } + receipts := make(types.Receipts, len(storageReceipts)) + for i, receipt := range storageReceipts { + receipts[i] = (*types.Receipt)(receipt) + } + return receipts +} + +// WriteReceipts stores all the transaction receipts belonging to a block. +func WriteReceipts(db DatabaseWriter, hash common.Hash, number uint64, receipts types.Receipts) { + // Convert the receipts into their storage form and serialize them + storageReceipts := make([]*types.ReceiptForStorage, len(receipts)) + for i, receipt := range receipts { + storageReceipts[i] = (*types.ReceiptForStorage)(receipt) + } + bytes, err := rlp.EncodeToBytes(storageReceipts) + if err != nil { + log.Crit("Failed to encode block receipts", "err", err) + } + // Store the flattened receipt slice + key := append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) + if err := db.Put(key, bytes); err != nil { + log.Crit("Failed to store block receipts", "err", err) + } +} + +// DeleteReceipts removes all receipt data associated with a block hash. +func DeleteReceipts(db DatabaseDeleter, hash common.Hash, number uint64) { + if err := db.Delete(append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...)); err != nil { + log.Crit("Failed to delete block receipts", "err", err) + } +} + +// ReadBlock retrieves an entire block corresponding to the hash, assembling it +// back from the stored header and body. If either the header or body could not +// be retrieved nil is returned. +// +// Note, due to concurrent download of header and block body the header and thus +// canonical hash can be stored in the database but the body data not (yet). +func ReadBlock(db DatabaseReader, hash common.Hash, number uint64) *types.Block { + header := ReadHeader(db, hash, number) + if header == nil { + return nil + } + body := ReadBody(db, hash, number) + if body == nil { + return nil + } + return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles) +} + +// WriteBlock serializes a block into the database, header and body separately. +func WriteBlock(db DatabaseWriter, block *types.Block) { + WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + WriteHeader(db, block.Header()) +} + +// DeleteBlock removes all block data associated with a hash. +func DeleteBlock(db DatabaseDeleter, hash common.Hash, number uint64) { + DeleteReceipts(db, hash, number) + DeleteHeader(db, hash, number) + DeleteBody(db, hash, number) + DeleteTd(db, hash, number) +} + +// FindCommonAncestor returns the last common ancestor of two block headers +func FindCommonAncestor(db DatabaseReader, a, b *types.Header) *types.Header { + for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { + a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) + if a == nil { + return nil + } + } + for an := a.Number.Uint64(); an < b.Number.Uint64(); { + b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) + if b == nil { + return nil + } + } + for a.Hash() != b.Hash() { + a = ReadHeader(db, a.ParentHash, a.Number.Uint64()-1) + if a == nil { + return nil + } + b = ReadHeader(db, b.ParentHash, b.Number.Uint64()-1) + if b == nil { + return nil + } + } + return a +} diff --git a/core/database_util_test.go b/core/rawdb/accessors_chain_test.go similarity index 59% rename from core/database_util_test.go rename to core/rawdb/accessors_chain_test.go index aa87fa6f86..9ddae6e2b5 100644 --- a/core/database_util_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The go-ethereum Authors +// Copyright 2018 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 @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package core +package rawdb import ( "bytes" @@ -30,23 +30,21 @@ import ( // Tests block header storage and retrieval operations. func TestHeaderStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() // Create a test header to move around the database and make sure it's really new header := &types.Header{Number: big.NewInt(42), Extra: []byte("test header")} - if entry := GetHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { t.Fatalf("Non existent header returned: %v", entry) } // Write and verify the header in the database - if err := WriteHeader(db, header); err != nil { - t.Fatalf("Failed to write header into database: %v", err) - } - if entry := GetHeader(db, header.Hash(), header.Number.Uint64()); entry == nil { + WriteHeader(db, header) + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry == nil { t.Fatalf("Stored header not found") } else if entry.Hash() != header.Hash() { t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, header) } - if entry := GetHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil { + if entry := ReadHeaderRLP(db, header.Hash(), header.Number.Uint64()); entry == nil { t.Fatalf("Stored header RLP not found") } else { hasher := sha3.NewKeccak256() @@ -58,14 +56,14 @@ func TestHeaderStorage(t *testing.T) { } // Delete the header and verify the execution DeleteHeader(db, header.Hash(), header.Number.Uint64()) - if entry := GetHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { + if entry := ReadHeader(db, header.Hash(), header.Number.Uint64()); entry != nil { t.Fatalf("Deleted header returned: %v", entry) } } // Tests block body storage and retrieval operations. func TestBodyStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() // Create a test body to move around the database and make sure it's really new body := &types.Body{Uncles: []*types.Header{{Extra: []byte("test header")}}} @@ -74,19 +72,17 @@ func TestBodyStorage(t *testing.T) { rlp.Encode(hasher, body) hash := common.BytesToHash(hasher.Sum(nil)) - if entry := GetBody(db, hash, 0); entry != nil { + if entry := ReadBody(db, hash, 0); entry != nil { t.Fatalf("Non existent body returned: %v", entry) } // Write and verify the body in the database - if err := WriteBody(db, hash, 0, body); err != nil { - t.Fatalf("Failed to write body into database: %v", err) - } - if entry := GetBody(db, hash, 0); entry == nil { + WriteBody(db, hash, 0, body) + if entry := ReadBody(db, hash, 0); entry == nil { t.Fatalf("Stored body not found") } else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(types.Transactions(body.Transactions)) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body) } - if entry := GetBodyRLP(db, hash, 0); entry == nil { + if entry := ReadBodyRLP(db, hash, 0); entry == nil { t.Fatalf("Stored body RLP not found") } else { hasher := sha3.NewKeccak256() @@ -98,14 +94,14 @@ func TestBodyStorage(t *testing.T) { } // Delete the body and verify the execution DeleteBody(db, hash, 0) - if entry := GetBody(db, hash, 0); entry != nil { + if entry := ReadBody(db, hash, 0); entry != nil { t.Fatalf("Deleted body returned: %v", entry) } } // Tests block storage and retrieval operations. func TestBlockStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() // Create a test block to move around the database and make sure it's really new block := types.NewBlockWithHeader(&types.Header{ @@ -114,50 +110,48 @@ func TestBlockStorage(t *testing.T) { TxHash: types.EmptyRootHash, ReceiptHash: types.EmptyRootHash, }) - if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil { + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent block returned: %v", entry) } - if entry := GetHeader(db, block.Hash(), block.NumberU64()); entry != nil { + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent header returned: %v", entry) } - if entry := GetBody(db, block.Hash(), block.NumberU64()); entry != nil { + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent body returned: %v", entry) } // Write and verify the block in the database - if err := WriteBlock(db, block); err != nil { - t.Fatalf("Failed to write block into database: %v", err) - } - if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry == nil { + WriteBlock(db, block) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored block not found") } else if entry.Hash() != block.Hash() { t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) } - if entry := GetHeader(db, block.Hash(), block.NumberU64()); entry == nil { + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored header not found") } else if entry.Hash() != block.Header().Hash() { t.Fatalf("Retrieved header mismatch: have %v, want %v", entry, block.Header()) } - if entry := GetBody(db, block.Hash(), block.NumberU64()); entry == nil { + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored body not found") } else if types.DeriveSha(types.Transactions(entry.Transactions)) != types.DeriveSha(block.Transactions()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body()) } // Delete the block and verify the execution DeleteBlock(db, block.Hash(), block.NumberU64()) - if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil { + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Deleted block returned: %v", entry) } - if entry := GetHeader(db, block.Hash(), block.NumberU64()); entry != nil { + if entry := ReadHeader(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Deleted header returned: %v", entry) } - if entry := GetBody(db, block.Hash(), block.NumberU64()); entry != nil { + if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Deleted body returned: %v", entry) } } // Tests that partial block contents don't get reassembled into full blocks. func TestPartialBlockStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() block := types.NewBlockWithHeader(&types.Header{ Extra: []byte("test block"), UncleHash: types.EmptyUncleHash, @@ -165,31 +159,24 @@ func TestPartialBlockStorage(t *testing.T) { ReceiptHash: types.EmptyRootHash, }) // Store a header and check that it's not recognized as a block - if err := WriteHeader(db, block.Header()); err != nil { - t.Fatalf("Failed to write header into database: %v", err) - } - if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil { + WriteHeader(db, block.Header()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent block returned: %v", entry) } DeleteHeader(db, block.Hash(), block.NumberU64()) // Store a body and check that it's not recognized as a block - if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil { - t.Fatalf("Failed to write body into database: %v", err) - } - if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry != nil { + WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry != nil { t.Fatalf("Non existent block returned: %v", entry) } DeleteBody(db, block.Hash(), block.NumberU64()) // Store a header and a body separately and check reassembly - if err := WriteHeader(db, block.Header()); err != nil { - t.Fatalf("Failed to write header into database: %v", err) - } - if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil { - t.Fatalf("Failed to write body into database: %v", err) - } - if entry := GetBlock(db, block.Hash(), block.NumberU64()); entry == nil { + WriteHeader(db, block.Header()) + WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) + + if entry := ReadBlock(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored block not found") } else if entry.Hash() != block.Hash() { t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block) @@ -198,142 +185,88 @@ func TestPartialBlockStorage(t *testing.T) { // Tests block total difficulty storage and retrieval operations. func TestTdStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() // Create a test TD to move around the database and make sure it's really new hash, td := common.Hash{}, big.NewInt(314) - if entry := GetTd(db, hash, 0); entry != nil { + if entry := ReadTd(db, hash, 0); entry != nil { t.Fatalf("Non existent TD returned: %v", entry) } // Write and verify the TD in the database - if err := WriteTd(db, hash, 0, td); err != nil { - t.Fatalf("Failed to write TD into database: %v", err) - } - if entry := GetTd(db, hash, 0); entry == nil { + WriteTd(db, hash, 0, td) + if entry := ReadTd(db, hash, 0); entry == nil { t.Fatalf("Stored TD not found") } else if entry.Cmp(td) != 0 { t.Fatalf("Retrieved TD mismatch: have %v, want %v", entry, td) } // Delete the TD and verify the execution DeleteTd(db, hash, 0) - if entry := GetTd(db, hash, 0); entry != nil { + if entry := ReadTd(db, hash, 0); entry != nil { t.Fatalf("Deleted TD returned: %v", entry) } } // Tests that canonical numbers can be mapped to hashes and retrieved. func TestCanonicalMappingStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() // Create a test canonical number and assinged hash to move around hash, number := common.Hash{0: 0xff}, uint64(314) - if entry := GetCanonicalHash(db, number); entry != (common.Hash{}) { + if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) { t.Fatalf("Non existent canonical mapping returned: %v", entry) } // Write and verify the TD in the database - if err := WriteCanonicalHash(db, hash, number); err != nil { - t.Fatalf("Failed to write canonical mapping into database: %v", err) - } - if entry := GetCanonicalHash(db, number); entry == (common.Hash{}) { + WriteCanonicalHash(db, hash, number) + if entry := ReadCanonicalHash(db, number); entry == (common.Hash{}) { t.Fatalf("Stored canonical mapping not found") } else if entry != hash { t.Fatalf("Retrieved canonical mapping mismatch: have %v, want %v", entry, hash) } // Delete the TD and verify the execution DeleteCanonicalHash(db, number) - if entry := GetCanonicalHash(db, number); entry != (common.Hash{}) { + if entry := ReadCanonicalHash(db, number); entry != (common.Hash{}) { t.Fatalf("Deleted canonical mapping returned: %v", entry) } } // Tests that head headers and head blocks can be assigned, individually. func TestHeadStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() blockHead := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block header")}) blockFull := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block full")}) blockFast := types.NewBlockWithHeader(&types.Header{Extra: []byte("test block fast")}) // Check that no head entries are in a pristine database - if entry := GetHeadHeaderHash(db); entry != (common.Hash{}) { + if entry := ReadHeadHeaderHash(db); entry != (common.Hash{}) { t.Fatalf("Non head header entry returned: %v", entry) } - if entry := GetHeadBlockHash(db); entry != (common.Hash{}) { + if entry := ReadHeadBlockHash(db); entry != (common.Hash{}) { t.Fatalf("Non head block entry returned: %v", entry) } - if entry := GetHeadFastBlockHash(db); entry != (common.Hash{}) { + if entry := ReadHeadFastBlockHash(db); entry != (common.Hash{}) { t.Fatalf("Non fast head block entry returned: %v", entry) } // Assign separate entries for the head header and block - if err := WriteHeadHeaderHash(db, blockHead.Hash()); err != nil { - t.Fatalf("Failed to write head header hash: %v", err) - } - if err := WriteHeadBlockHash(db, blockFull.Hash()); err != nil { - t.Fatalf("Failed to write head block hash: %v", err) - } - if err := WriteHeadFastBlockHash(db, blockFast.Hash()); err != nil { - t.Fatalf("Failed to write fast head block hash: %v", err) - } + WriteHeadHeaderHash(db, blockHead.Hash()) + WriteHeadBlockHash(db, blockFull.Hash()) + WriteHeadFastBlockHash(db, blockFast.Hash()) + // Check that both heads are present, and different (i.e. two heads maintained) - if entry := GetHeadHeaderHash(db); entry != blockHead.Hash() { + if entry := ReadHeadHeaderHash(db); entry != blockHead.Hash() { t.Fatalf("Head header hash mismatch: have %v, want %v", entry, blockHead.Hash()) } - if entry := GetHeadBlockHash(db); entry != blockFull.Hash() { + if entry := ReadHeadBlockHash(db); entry != blockFull.Hash() { t.Fatalf("Head block hash mismatch: have %v, want %v", entry, blockFull.Hash()) } - if entry := GetHeadFastBlockHash(db); entry != blockFast.Hash() { + if entry := ReadHeadFastBlockHash(db); entry != blockFast.Hash() { t.Fatalf("Fast head block hash mismatch: have %v, want %v", entry, blockFast.Hash()) } } -// Tests that positional lookup metadata can be stored and retrieved. -func TestLookupStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - - tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) - tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) - tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) - txs := []*types.Transaction{tx1, tx2, tx3} - - block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil) - - // Check that no transactions entries are in a pristine database - for i, tx := range txs { - if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil { - t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) - } - } - // Insert all the transactions into the database, and verify contents - if err := WriteBlock(db, block); err != nil { - t.Fatalf("failed to write block contents: %v", err) - } - if err := WriteTxLookupEntries(db, block); err != nil { - t.Fatalf("failed to write transactions: %v", err) - } - for i, tx := range txs { - if txn, hash, number, index := GetTransaction(db, tx.Hash()); txn == nil { - t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash()) - } else { - if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { - t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i) - } - if tx.Hash() != txn.Hash() { - t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) - } - } - } - // Delete the transactions and check purge - for i, tx := range txs { - DeleteTxLookupEntry(db, tx.Hash()) - if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil { - t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) - } - } -} - // Tests that receipts associated with a single block can be stored and retrieved. func TestBlockReceiptStorage(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() receipt1 := &types.Receipt{ Status: types.ReceiptStatusFailed, @@ -361,14 +294,12 @@ func TestBlockReceiptStorage(t *testing.T) { // Check that no receipt entries are in a pristine database hash := common.BytesToHash([]byte{0x03, 0x14}) - if rs := GetBlockReceipts(db, hash, 0); len(rs) != 0 { + if rs := ReadReceipts(db, hash, 0); len(rs) != 0 { t.Fatalf("non existent receipts returned: %v", rs) } // Insert the receipt slice into the database and check presence - if err := WriteBlockReceipts(db, hash, 0, receipts); err != nil { - t.Fatalf("failed to write block receipts: %v", err) - } - if rs := GetBlockReceipts(db, hash, 0); len(rs) == 0 { + WriteReceipts(db, hash, 0, receipts) + if rs := ReadReceipts(db, hash, 0); len(rs) == 0 { t.Fatalf("no receipts returned") } else { for i := 0; i < len(receipts); i++ { @@ -381,8 +312,8 @@ func TestBlockReceiptStorage(t *testing.T) { } } // Delete the receipt slice and check purge - DeleteBlockReceipts(db, hash, 0) - if rs := GetBlockReceipts(db, hash, 0); len(rs) != 0 { + DeleteReceipts(db, hash, 0) + if rs := ReadReceipts(db, hash, 0); len(rs) != 0 { t.Fatalf("deleted receipts returned: %v", rs) } } diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go new file mode 100644 index 0000000000..9abad14e0c --- /dev/null +++ b/core/rawdb/accessors_indexes.go @@ -0,0 +1,119 @@ +// Copyright 2018 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 . + +package rawdb + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// ReadTxLookupEntry retrieves the positional metadata associated with a transaction +// hash to allow retrieving the transaction or receipt by hash. +func ReadTxLookupEntry(db DatabaseReader, hash common.Hash) (common.Hash, uint64, uint64) { + data, _ := db.Get(append(txLookupPrefix, hash.Bytes()...)) + if len(data) == 0 { + return common.Hash{}, 0, 0 + } + var entry TxLookupEntry + if err := rlp.DecodeBytes(data, &entry); err != nil { + log.Error("Invalid transaction lookup entry RLP", "hash", hash, "err", err) + return common.Hash{}, 0, 0 + } + return entry.BlockHash, entry.BlockIndex, entry.Index +} + +// WriteTxLookupEntries stores a positional metadata for every transaction from +// a block, enabling hash based transaction and receipt lookups. +func WriteTxLookupEntries(db DatabaseWriter, block *types.Block) { + for i, tx := range block.Transactions() { + entry := TxLookupEntry{ + BlockHash: block.Hash(), + BlockIndex: block.NumberU64(), + Index: uint64(i), + } + data, err := rlp.EncodeToBytes(entry) + if err != nil { + log.Crit("Failed to encode transaction lookup entry", "err", err) + } + if err := db.Put(append(txLookupPrefix, tx.Hash().Bytes()...), data); err != nil { + log.Crit("Failed to store transaction lookup entry", "err", err) + } + } +} + +// DeleteTxLookupEntry removes all transaction data associated with a hash. +func DeleteTxLookupEntry(db DatabaseDeleter, hash common.Hash) { + db.Delete(append(txLookupPrefix, hash.Bytes()...)) +} + +// ReadTransaction retrieves a specific transaction from the database, along with +// its added positional metadata. +func ReadTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) { + blockHash, blockNumber, txIndex := ReadTxLookupEntry(db, hash) + if blockHash == (common.Hash{}) { + return nil, common.Hash{}, 0, 0 + } + body := ReadBody(db, blockHash, blockNumber) + if body == nil || len(body.Transactions) <= int(txIndex) { + log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex) + return nil, common.Hash{}, 0, 0 + } + return body.Transactions[txIndex], blockHash, blockNumber, txIndex +} + +// ReadReceipt retrieves a specific transaction receipt from the database, along with +// its added positional metadata. +func ReadReceipt(db DatabaseReader, hash common.Hash) (*types.Receipt, common.Hash, uint64, uint64) { + blockHash, blockNumber, receiptIndex := ReadTxLookupEntry(db, hash) + if blockHash == (common.Hash{}) { + return nil, common.Hash{}, 0, 0 + } + receipts := ReadReceipts(db, blockHash, blockNumber) + if len(receipts) <= int(receiptIndex) { + log.Error("Receipt refereced missing", "number", blockNumber, "hash", blockHash, "index", receiptIndex) + return nil, common.Hash{}, 0, 0 + } + return receipts[receiptIndex], blockHash, blockNumber, receiptIndex +} + +// ReadBloomBits retrieves the compressed bloom bit vector belonging to the given +// section and bit index from the. +func ReadBloomBits(db DatabaseReader, bit uint, section uint64, head common.Hash) ([]byte, error) { + key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...) + + binary.BigEndian.PutUint16(key[1:], uint16(bit)) + binary.BigEndian.PutUint64(key[3:], section) + + return db.Get(key) +} + +// WriteBloomBits stores the compressed bloom bits vector belonging to the given +// section and bit index. +func WriteBloomBits(db DatabaseWriter, bit uint, section uint64, head common.Hash, bits []byte) { + key := append(append(bloomBitsPrefix, make([]byte, 10)...), head.Bytes()...) + + binary.BigEndian.PutUint16(key[1:], uint16(bit)) + binary.BigEndian.PutUint64(key[3:], section) + + if err := db.Put(key, bits); err != nil { + log.Crit("Failed to store bloom bits", "err", err) + } +} diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go new file mode 100644 index 0000000000..d9c10e1490 --- /dev/null +++ b/core/rawdb/accessors_indexes_test.go @@ -0,0 +1,68 @@ +// Copyright 2018 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 . + +package rawdb + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" +) + +// Tests that positional lookup metadata can be stored and retrieved. +func TestLookupStorage(t *testing.T) { + db := ethdb.NewMemDatabase() + + tx1 := types.NewTransaction(1, common.BytesToAddress([]byte{0x11}), big.NewInt(111), 1111, big.NewInt(11111), []byte{0x11, 0x11, 0x11}) + tx2 := types.NewTransaction(2, common.BytesToAddress([]byte{0x22}), big.NewInt(222), 2222, big.NewInt(22222), []byte{0x22, 0x22, 0x22}) + tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) + txs := []*types.Transaction{tx1, tx2, tx3} + + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil) + + // Check that no transactions entries are in a pristine database + for i, tx := range txs { + if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + t.Fatalf("tx #%d [%x]: non existent transaction returned: %v", i, tx.Hash(), txn) + } + } + // Insert all the transactions into the database, and verify contents + WriteBlock(db, block) + WriteTxLookupEntries(db, block) + + for i, tx := range txs { + if txn, hash, number, index := ReadTransaction(db, tx.Hash()); txn == nil { + t.Fatalf("tx #%d [%x]: transaction not found", i, tx.Hash()) + } else { + if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { + t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i) + } + if tx.Hash() != txn.Hash() { + t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) + } + } + } + // Delete the transactions and check purge + for i, tx := range txs { + DeleteTxLookupEntry(db, tx.Hash()) + if txn, _, _, _ := ReadTransaction(db, tx.Hash()); txn != nil { + t.Fatalf("tx #%d [%x]: deleted transaction returned: %v", i, tx.Hash(), txn) + } + } +} diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go new file mode 100644 index 0000000000..73ab983f2b --- /dev/null +++ b/core/rawdb/accessors_metadata.go @@ -0,0 +1,90 @@ +// Copyright 2018 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 . + +package rawdb + +import ( + "encoding/json" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// ReadDatabaseVersion retrieves the version number of the database. +func ReadDatabaseVersion(db DatabaseReader) int { + var version int + + enc, _ := db.Get(databaseVerisionKey) + rlp.DecodeBytes(enc, &version) + + return version +} + +// WriteDatabaseVersion stores the version number of the database +func WriteDatabaseVersion(db DatabaseWriter, version int) { + enc, _ := rlp.EncodeToBytes(version) + if err := db.Put(databaseVerisionKey, enc); err != nil { + log.Crit("Failed to store the database version", "err", err) + } +} + +// ReadChainConfig retrieves the consensus settings based on the given genesis hash. +func ReadChainConfig(db DatabaseReader, hash common.Hash) *params.ChainConfig { + data, _ := db.Get(append(configPrefix, hash[:]...)) + if len(data) == 0 { + return nil + } + var config params.ChainConfig + if err := json.Unmarshal(data, &config); err != nil { + log.Error("Invalid chain config JSON", "hash", hash, "err", err) + return nil + } + return &config +} + +// WriteChainConfig writes the chain config settings to the database. +func WriteChainConfig(db DatabaseWriter, hash common.Hash, cfg *params.ChainConfig) { + if cfg == nil { + return + } + data, err := json.Marshal(cfg) + if err != nil { + log.Crit("Failed to JSON encode chain config", "err", err) + } + if err := db.Put(append(configPrefix, hash[:]...), data); err != nil { + log.Crit("Failed to store chain config", "err", err) + } +} + +// ReadPreimage retrieves a single preimage of the provided hash. +func ReadPreimage(db DatabaseReader, hash common.Hash) []byte { + data, _ := db.Get(append(preimagePrefix, hash.Bytes()...)) + return data +} + +// WritePreimages writes the provided set of preimages to the database. `number` is the +// current block number, and is used for debug messages only. +func WritePreimages(db DatabaseWriter, number uint64, preimages map[common.Hash][]byte) { + for hash, preimage := range preimages { + if err := db.Put(append(preimagePrefix, hash.Bytes()...), preimage); err != nil { + log.Crit("Failed to store trie preimage", "err", err) + } + } + preimageCounter.Inc(int64(len(preimages))) + preimageHitCounter.Inc(int64(len(preimages))) +} diff --git a/core/rawdb/interfaces.go b/core/rawdb/interfaces.go new file mode 100644 index 0000000000..3bdf55124a --- /dev/null +++ b/core/rawdb/interfaces.go @@ -0,0 +1,33 @@ +// Copyright 2018 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 . + +package rawdb + +// DatabaseReader wraps the Has and Get method of a backing data store. +type DatabaseReader interface { + Has(key []byte) (bool, error) + Get(key []byte) ([]byte, error) +} + +// DatabaseWriter wraps the Put method of a backing data store. +type DatabaseWriter interface { + Put(key []byte, value []byte) error +} + +// DatabaseDeleter wraps the Delete method of a backing data store. +type DatabaseDeleter interface { + Delete(key []byte) error +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go new file mode 100644 index 0000000000..a4b1596fde --- /dev/null +++ b/core/rawdb/schema.go @@ -0,0 +1,79 @@ +// Copyright 2018 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 . + +// Package rawdb contains a collection of low level database accessors. +package rawdb + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/metrics" +) + +// The fields below define the low level database schema prefixing. +var ( + // databaseVerisionKey tracks the current database version. + databaseVerisionKey = []byte("DatabaseVersion") + + // headHeaderKey tracks the latest know header's hash. + headHeaderKey = []byte("LastHeader") + + // headBlockKey tracks the latest know full block's hash. + headBlockKey = []byte("LastBlock") + + // headFastBlockKey tracks the latest known incomplete block's hash duirng fast sync. + headFastBlockKey = []byte("LastFast") + + // fastTrieProgressKey tracks the number of trie entries imported during fast sync. + fastTrieProgressKey = []byte("TrieSync") + + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). + headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header + headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td + headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash + headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian) + + blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body + blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts + + txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata + bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits + + preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage + configPrefix = []byte("ethereum-config-") // config prefix for the db + + // Chain index prefixes (use `i` + single byte to avoid mixing data types). + BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress + + preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) + preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) +) + +// TxLookupEntry is a positional metadata to help looking up the data content of +// a transaction or receipt given only its hash. +type TxLookupEntry struct { + BlockHash common.Hash + BlockIndex uint64 + Index uint64 +} + +// encodeBlockNumber encodes a block number as big endian uint64 +func encodeBlockNumber(number uint64) []byte { + enc := make([]byte, 8) + binary.BigEndian.PutUint64(enc, number) + return enc +} diff --git a/core/state/dump.go b/core/state/dump.go index 46e612850a..072dbbf053 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -53,7 +53,7 @@ func (self *StateDB) RawDump() Dump { panic(err) } - obj := newObject(nil, common.BytesToAddress(addr), data, nil) + obj := newObject(nil, common.BytesToAddress(addr), data) account := DumpAccount{ Balance: data.Balance.String(), Nonce: data.Nonce, diff --git a/core/state/journal.go b/core/state/journal.go index a89bb3d13a..a03ca57dbc 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -22,11 +22,67 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// journalEntry is a modification entry in the state change journal that can be +// reverted on demand. type journalEntry interface { - undo(*StateDB) + // revert undoes the changes introduced by this journal entry. + revert(*StateDB) + + // dirtied returns the Ethereum address modified by this journal entry. + dirtied() *common.Address } -type journal []journalEntry +// journal contains the list of state modifications applied since the last state +// commit. These are tracked to be able to be reverted in case of an execution +// exception or revertal request. +type journal struct { + entries []journalEntry // Current changes tracked by the journal + dirties map[common.Address]int // Dirty accounts and the number of changes +} + +// newJournal create a new initialized journal. +func newJournal() *journal { + return &journal{ + dirties: make(map[common.Address]int), + } +} + +// append inserts a new modification entry to the end of the change journal. +func (j *journal) append(entry journalEntry) { + j.entries = append(j.entries, entry) + if addr := entry.dirtied(); addr != nil { + j.dirties[*addr]++ + } +} + +// revert undoes a batch of journalled modifications along with any reverted +// dirty handling too. +func (j *journal) revert(statedb *StateDB, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].revert(statedb) + + // Drop any dirty tracking induced by the change + if addr := j.entries[i].dirtied(); addr != nil { + if j.dirties[*addr]--; j.dirties[*addr] == 0 { + delete(j.dirties, *addr) + } + } + } + j.entries = j.entries[:snapshot] +} + +// dirty explicitly sets an address to dirty, even if the change entries would +// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD +// precompile consensus exception. +func (j *journal) dirty(addr common.Address) { + j.dirties[addr]++ +} + +// length returns the current number of entries in the journal. +func (j *journal) length() int { + return len(j.entries) +} type ( // Changes to the account trie. @@ -77,16 +133,24 @@ type ( } ) -func (ch createObjectChange) undo(s *StateDB) { +func (ch createObjectChange) revert(s *StateDB) { delete(s.stateObjects, *ch.account) delete(s.stateObjectsDirty, *ch.account) } -func (ch resetObjectChange) undo(s *StateDB) { +func (ch createObjectChange) dirtied() *common.Address { + return ch.account +} + +func (ch resetObjectChange) revert(s *StateDB) { s.setStateObject(ch.prev) } -func (ch suicideChange) undo(s *StateDB) { +func (ch resetObjectChange) dirtied() *common.Address { + return nil +} + +func (ch suicideChange) revert(s *StateDB) { obj := s.getStateObject(*ch.account) if obj != nil { obj.suicided = ch.prev @@ -94,38 +158,60 @@ func (ch suicideChange) undo(s *StateDB) { } } -var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") - -func (ch touchChange) undo(s *StateDB) { - if !ch.prev && *ch.account != ripemd { - s.getStateObject(*ch.account).touched = ch.prev - if !ch.prevDirty { - delete(s.stateObjectsDirty, *ch.account) - } - } +func (ch suicideChange) dirtied() *common.Address { + return ch.account } -func (ch balanceChange) undo(s *StateDB) { +var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") + +func (ch touchChange) revert(s *StateDB) { +} + +func (ch touchChange) dirtied() *common.Address { + return ch.account +} + +func (ch balanceChange) revert(s *StateDB) { s.getStateObject(*ch.account).setBalance(ch.prev) } -func (ch nonceChange) undo(s *StateDB) { +func (ch balanceChange) dirtied() *common.Address { + return ch.account +} + +func (ch nonceChange) revert(s *StateDB) { s.getStateObject(*ch.account).setNonce(ch.prev) } -func (ch codeChange) undo(s *StateDB) { +func (ch nonceChange) dirtied() *common.Address { + return ch.account +} + +func (ch codeChange) revert(s *StateDB) { s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } -func (ch storageChange) undo(s *StateDB) { +func (ch codeChange) dirtied() *common.Address { + return ch.account +} + +func (ch storageChange) revert(s *StateDB) { s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } -func (ch refundChange) undo(s *StateDB) { +func (ch storageChange) dirtied() *common.Address { + return ch.account +} + +func (ch refundChange) revert(s *StateDB) { s.refund = ch.prev } -func (ch addLogChange) undo(s *StateDB) { +func (ch refundChange) dirtied() *common.Address { + return nil +} + +func (ch addLogChange) revert(s *StateDB) { logs := s.logs[ch.txhash] if len(logs) == 1 { delete(s.logs, ch.txhash) @@ -135,6 +221,14 @@ func (ch addLogChange) undo(s *StateDB) { s.logSize-- } -func (ch addPreimageChange) undo(s *StateDB) { +func (ch addLogChange) dirtied() *common.Address { + return nil +} + +func (ch addPreimageChange) revert(s *StateDB) { delete(s.preimages, ch.hash) } + +func (ch addPreimageChange) dirtied() *common.Address { + return nil +} diff --git a/core/state/managed_state_test.go b/core/state/managed_state_test.go index 1cfdd3a890..3d9c4e8676 100644 --- a/core/state/managed_state_test.go +++ b/core/state/managed_state_test.go @@ -26,8 +26,7 @@ import ( var addr = common.BytesToAddress([]byte("test")) func create() (*ManagedState, *account) { - db, _ := ethdb.NewMemDatabase() - statedb, _ := New(common.Hash{}, NewDatabase(db)) + statedb, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase())) ms := ManageState(statedb) ms.StateDB.SetNonce(addr, 100) ms.accounts[addr] = newAccount(ms.StateDB.getStateObject(addr)) diff --git a/core/state/state_object.go b/core/state/state_object.go index b2112bfaec..5d203fddd8 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -85,9 +85,7 @@ type stateObject struct { // during the "update" phase of the state transition. dirtyCode bool // true if the code was updated suicided bool - touched bool deleted bool - onDirty func(addr common.Address) // Callback method to mark a state object newly dirty } // empty returns whether the account is considered empty. @@ -105,7 +103,7 @@ type Account struct { } // newObject creates a state object. -func newObject(db *StateDB, address common.Address, data Account, onDirty func(addr common.Address)) *stateObject { +func newObject(db *StateDB, address common.Address, data Account) *stateObject { if data.Balance == nil { data.Balance = new(big.Int) } @@ -119,7 +117,6 @@ func newObject(db *StateDB, address common.Address, data Account, onDirty func(a data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), - onDirty: onDirty, } } @@ -137,23 +134,17 @@ func (self *stateObject) setError(err error) { func (self *stateObject) markSuicided() { self.suicided = true - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } func (c *stateObject) touch() { - c.db.journal = append(c.db.journal, touchChange{ - account: &c.address, - prev: c.touched, - prevDirty: c.onDirty == nil, + c.db.journal.append(touchChange{ + account: &c.address, }) - if c.onDirty != nil { - c.onDirty(c.Address()) - c.onDirty = nil + if c.address == ripemd { + // Explicitly put it in the dirty-cache, which is otherwise generated from + // flattened journals. + c.db.journal.dirty(c.address) } - c.touched = true } func (c *stateObject) getTrie(db Database) Trie { @@ -187,15 +178,13 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash { } value.SetBytes(content) } - if (value != common.Hash{}) { - self.cachedStorage[key] = value - } + self.cachedStorage[key] = value return value } // SetState updates a value in account storage. func (self *stateObject) SetState(db Database, key, value common.Hash) { - self.db.journal = append(self.db.journal, storageChange{ + self.db.journal.append(storageChange{ account: &self.address, key: key, prevalue: self.GetState(db, key), @@ -206,11 +195,6 @@ func (self *stateObject) SetState(db Database, key, value common.Hash) { func (self *stateObject) setState(key, value common.Hash) { self.cachedStorage[key] = value self.dirtyStorage[key] = value - - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } // updateTrie writes cached storage modifications into the object's storage trie. @@ -274,7 +258,7 @@ func (c *stateObject) SubBalance(amount *big.Int) { } func (self *stateObject) SetBalance(amount *big.Int) { - self.db.journal = append(self.db.journal, balanceChange{ + self.db.journal.append(balanceChange{ account: &self.address, prev: new(big.Int).Set(self.data.Balance), }) @@ -283,17 +267,13 @@ func (self *stateObject) SetBalance(amount *big.Int) { func (self *stateObject) setBalance(amount *big.Int) { self.data.Balance = amount - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } // Return the gas back to the origin. Used by the Virtual machine or Closures func (c *stateObject) ReturnGas(gas *big.Int) {} -func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject { - stateObject := newObject(db, self.address, self.data, onDirty) +func (self *stateObject) deepCopy(db *StateDB) *stateObject { + stateObject := newObject(db, self.address, self.data) if self.trie != nil { stateObject.trie = db.db.CopyTrie(self.trie) } @@ -333,7 +313,7 @@ func (self *stateObject) Code(db Database) []byte { func (self *stateObject) SetCode(codeHash common.Hash, code []byte) { prevcode := self.Code(self.db.db) - self.db.journal = append(self.db.journal, codeChange{ + self.db.journal.append(codeChange{ account: &self.address, prevhash: self.CodeHash(), prevcode: prevcode, @@ -345,14 +325,10 @@ func (self *stateObject) setCode(codeHash common.Hash, code []byte) { self.code = code self.data.CodeHash = codeHash[:] self.dirtyCode = true - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } func (self *stateObject) SetNonce(nonce uint64) { - self.db.journal = append(self.db.journal, nonceChange{ + self.db.journal.append(nonceChange{ account: &self.address, prev: self.data.Nonce, }) @@ -361,10 +337,6 @@ func (self *stateObject) SetNonce(nonce uint64) { func (self *stateObject) setNonce(nonce uint64) { self.data.Nonce = nonce - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } func (self *stateObject) CodeHash() []byte { diff --git a/core/state/state_test.go b/core/state/state_test.go index 6d42d63d82..12778f6f12 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -87,7 +87,7 @@ func (s *StateSuite) TestDump(c *checker.C) { } func (s *StateSuite) SetUpTest(c *checker.C) { - s.db, _ = ethdb.NewMemDatabase() + s.db = ethdb.NewMemDatabase() s.state, _ = New(common.Hash{}, NewDatabase(s.db)) } @@ -133,8 +133,7 @@ func (s *StateSuite) TestSnapshotEmpty(c *checker.C) { // use testing instead of checker because checker does not support // printing/logging in tests (-check.vv does not work) func TestSnapshot2(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - state, _ := New(common.Hash{}, NewDatabase(db)) + state, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase())) stateobjaddr0 := toAddr([]byte("so0")) stateobjaddr1 := toAddr([]byte("so1")) diff --git a/core/state/statedb.go b/core/state/statedb.go index bd67e789d5..a952027d6d 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -76,7 +76,7 @@ type StateDB struct { // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. - journal journal + journal *journal validRevisions []revision nextRevisionId int @@ -96,6 +96,7 @@ func New(root common.Hash, db Database) (*StateDB, error) { stateObjectsDirty: make(map[common.Address]struct{}), logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), + journal: newJournal(), }, nil } @@ -131,7 +132,7 @@ func (self *StateDB) Reset(root common.Hash) error { } func (self *StateDB) AddLog(log *types.Log) { - self.journal = append(self.journal, addLogChange{txhash: self.thash}) + self.journal.append(addLogChange{txhash: self.thash}) log.TxHash = self.thash log.BlockHash = self.bhash @@ -156,7 +157,7 @@ func (self *StateDB) Logs() []*types.Log { // AddPreimage records a SHA3 preimage seen by the VM. func (self *StateDB) AddPreimage(hash common.Hash, preimage []byte) { if _, ok := self.preimages[hash]; !ok { - self.journal = append(self.journal, addPreimageChange{hash: hash}) + self.journal.append(addPreimageChange{hash: hash}) pi := make([]byte, len(preimage)) copy(pi, preimage) self.preimages[hash] = pi @@ -169,7 +170,7 @@ func (self *StateDB) Preimages() map[common.Hash][]byte { } func (self *StateDB) AddRefund(gas uint64) { - self.journal = append(self.journal, refundChange{prev: self.refund}) + self.journal.append(refundChange{prev: self.refund}) self.refund += gas } @@ -255,7 +256,7 @@ func (self *StateDB) StorageTrie(addr common.Address) Trie { if stateObject == nil { return nil } - cpy := stateObject.deepCopy(self, nil) + cpy := stateObject.deepCopy(self) return cpy.updateTrie(self.db) } @@ -325,7 +326,7 @@ func (self *StateDB) Suicide(addr common.Address) bool { if stateObject == nil { return false } - self.journal = append(self.journal, suicideChange{ + self.journal.append(suicideChange{ account: &addr, prev: stateObject.suicided, prevbalance: new(big.Int).Set(stateObject.Balance()), @@ -379,7 +380,7 @@ func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObje return nil } // Insert into the live set. - obj := newObject(self, addr, data, self.MarkStateObjectDirty) + obj := newObject(self, addr, data) self.setStateObject(obj) return obj } @@ -397,22 +398,16 @@ func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { return stateObject } -// MarkStateObjectDirty adds the specified object to the dirty map to avoid costly -// state object cache iteration to find a handful of modified ones. -func (self *StateDB) MarkStateObjectDirty(addr common.Address) { - self.stateObjectsDirty[addr] = struct{}{} -} - // createObject creates a new state object. If there is an existing account with // the given address, it is overwritten and returned as the second return value. func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { prev = self.getStateObject(addr) - newobj = newObject(self, addr, Account{}, self.MarkStateObjectDirty) + newobj = newObject(self, addr, Account{}) newobj.setNonce(0) // sets the object to dirty if prev == nil { - self.journal = append(self.journal, createObjectChange{account: &addr}) + self.journal.append(createObjectChange{account: &addr}) } else { - self.journal = append(self.journal, resetObjectChange{prev: prev}) + self.journal.append(resetObjectChange{prev: prev}) } self.setStateObject(newobj) return newobj, prev @@ -466,18 +461,35 @@ func (self *StateDB) Copy() *StateDB { state := &StateDB{ db: self.db, trie: self.db.CopyTrie(self.trie), - stateObjects: make(map[common.Address]*stateObject, len(self.stateObjectsDirty)), - stateObjectsDirty: make(map[common.Address]struct{}, len(self.stateObjectsDirty)), + stateObjects: make(map[common.Address]*stateObject, len(self.journal.dirties)), + stateObjectsDirty: make(map[common.Address]struct{}, len(self.journal.dirties)), refund: self.refund, logs: make(map[common.Hash][]*types.Log, len(self.logs)), logSize: self.logSize, preimages: make(map[common.Hash][]byte), + journal: newJournal(), } // Copy the dirty states, logs, and preimages - for addr := range self.stateObjectsDirty { - state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state, state.MarkStateObjectDirty) - state.stateObjectsDirty[addr] = struct{}{} + for addr := range self.journal.dirties { + // As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527), + // and in the Finalise-method, there is a case where an object is in the journal but not + // in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for + // nil + if object, exist := self.stateObjects[addr]; exist { + state.stateObjects[addr] = object.deepCopy(state) + state.stateObjectsDirty[addr] = struct{}{} + } } + // Above, we don't copy the actual journal. This means that if the copy is copied, the + // loop above will be a no-op, since the copy's journal is empty. + // Thus, here we iterate over stateObjects, to enable copies of copies + for addr := range self.stateObjectsDirty { + if _, exist := state.stateObjects[addr]; !exist { + state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state) + state.stateObjectsDirty[addr] = struct{}{} + } + } + for hash, logs := range self.logs { state.logs[hash] = make([]*types.Log, len(logs)) copy(state.logs[hash], logs) @@ -492,7 +504,7 @@ func (self *StateDB) Copy() *StateDB { func (self *StateDB) Snapshot() int { id := self.nextRevisionId self.nextRevisionId++ - self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)}) + self.validRevisions = append(self.validRevisions, revision{id, self.journal.length()}) return id } @@ -507,13 +519,8 @@ func (self *StateDB) RevertToSnapshot(revid int) { } snapshot := self.validRevisions[idx].journalIndex - // Replay the journal to undo changes. - for i := len(self.journal) - 1; i >= snapshot; i-- { - self.journal[i].undo(self) - } - self.journal = self.journal[:snapshot] - - // Remove invalidated snapshots from the stack. + // Replay the journal to undo changes and remove invalidated snapshots + self.journal.revert(self, snapshot) self.validRevisions = self.validRevisions[:idx] } @@ -525,14 +532,25 @@ func (self *StateDB) GetRefund() uint64 { // Finalise finalises the state by removing the self destructed objects // and clears the journal as well as the refunds. func (s *StateDB) Finalise(deleteEmptyObjects bool) { - for addr := range s.stateObjectsDirty { - stateObject := s.stateObjects[addr] + for addr := range s.journal.dirties { + stateObject, exist := s.stateObjects[addr] + if !exist { + // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 + // That tx goes out of gas, and although the notion of 'touched' does not exist there, the + // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, + // it will persist in the journal even though the journal is reverted. In this special circumstance, + // it may exist in `s.journal.dirties` but not in `s.stateObjects`. + // Thus, we can safely ignore it here + continue + } + if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) { s.deleteStateObject(stateObject) } else { stateObject.updateRoot(s.db) s.updateStateObject(stateObject) } + s.stateObjectsDirty[addr] = struct{}{} } // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() @@ -554,29 +572,8 @@ func (self *StateDB) Prepare(thash, bhash common.Hash, ti int) { self.txIndex = ti } -// DeleteSuicides flags the suicided objects for deletion so that it -// won't be referenced again when called / queried up on. -// -// DeleteSuicides should not be used for consensus related updates -// under any circumstances. -func (s *StateDB) DeleteSuicides() { - // Reset refund so that any used-gas calculations can use this method. - s.clearJournalAndRefund() - - for addr := range s.stateObjectsDirty { - stateObject := s.stateObjects[addr] - - // If the object has been removed by a suicide - // flag the object as deleted. - if stateObject.suicided { - stateObject.deleted = true - } - delete(s.stateObjectsDirty, addr) - } -} - func (s *StateDB) clearJournalAndRefund() { - s.journal = nil + s.journal = newJournal() s.validRevisions = s.validRevisions[:0] s.refund = 0 } @@ -585,6 +582,9 @@ func (s *StateDB) clearJournalAndRefund() { func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) { defer s.clearJournalAndRefund() + for addr := range s.journal.dirties { + s.stateObjectsDirty[addr] = struct{}{} + } // Commit objects to the trie. for addr, stateObject := range s.stateObjects { _, isDirty := s.stateObjectsDirty[addr] diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index d9e3d9b797..e2b349de86 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -39,7 +39,7 @@ import ( // actually committing the state. func TestUpdateLeaks(t *testing.T) { // Create an empty state database - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() state, _ := New(common.Hash{}, NewDatabase(db)) // Update it with some accounts @@ -66,8 +66,8 @@ func TestUpdateLeaks(t *testing.T) { // only the one right before the commit. func TestIntermediateLeaks(t *testing.T) { // Create two state databases, one transitioning to the final state, the other final from the beginning - transDb, _ := ethdb.NewMemDatabase() - finalDb, _ := ethdb.NewMemDatabase() + transDb := ethdb.NewMemDatabase() + finalDb := ethdb.NewMemDatabase() transState, _ := New(common.Hash{}, NewDatabase(transDb)) finalState, _ := New(common.Hash{}, NewDatabase(finalDb)) @@ -122,8 +122,7 @@ func TestIntermediateLeaks(t *testing.T) { // https://github.com/ethereum/go-ethereum/pull/15549. func TestCopy(t *testing.T) { // Create a random state test to copy and modify "independently" - db, _ := ethdb.NewMemDatabase() - orig, _ := New(common.Hash{}, NewDatabase(db)) + orig, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase())) for i := byte(0); i < 255; i++ { obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) @@ -334,8 +333,7 @@ func (test *snapshotTest) String() string { func (test *snapshotTest) run() bool { // Run all actions and create snapshots. var ( - db, _ = ethdb.NewMemDatabase() - state, _ = New(common.Hash{}, NewDatabase(db)) + state, _ = New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase())) snapshotRevs = make([]int, len(test.snapshots)) sindex = 0 ) @@ -413,11 +411,27 @@ func (s *StateSuite) TestTouchDelete(c *check.C) { snapshot := s.state.Snapshot() s.state.AddBalance(common.Address{}, new(big.Int)) - if len(s.state.stateObjectsDirty) != 1 { + + if len(s.state.journal.dirties) != 1 { c.Fatal("expected one dirty state object") } s.state.RevertToSnapshot(snapshot) - if len(s.state.stateObjectsDirty) != 0 { + if len(s.state.journal.dirties) != 0 { c.Fatal("expected no dirty state object") } } + +// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. +// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 +func TestCopyOfCopy(t *testing.T) { + sdb, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase())) + addr := common.HexToAddress("aaaa") + sdb.SetBalance(addr, big.NewInt(42)) + + if got := sdb.Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("1st copy fail, expected 42, got %v", got) + } + if got := sdb.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("2nd copy fail, expected 42, got %v", got) + } +} diff --git a/core/state/sync_test.go b/core/state/sync_test.go index 8f14a44e7a..3177401608 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -38,8 +38,7 @@ type testAccount struct { // makeTestState create a sample test state to test node-wise reconstruction. func makeTestState() (Database, common.Hash, []*testAccount) { // Create an empty state - diskdb, _ := ethdb.NewMemDatabase() - db := NewDatabase(diskdb) + db := NewDatabase(ethdb.NewMemDatabase()) state, _ := New(common.Hash{}, db) // Fill it with some arbitrary data @@ -125,8 +124,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { // Tests that an empty state is not scheduled for syncing. func TestEmptyStateSync(t *testing.T) { empty := common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") - db, _ := ethdb.NewMemDatabase() - if req := NewStateSync(empty, db).Missing(1); len(req) != 0 { + if req := NewStateSync(empty, ethdb.NewMemDatabase()).Missing(1); len(req) != 0 { t.Errorf("content requested for empty state: %v", req) } } @@ -141,7 +139,7 @@ func testIterativeStateSync(t *testing.T, batch int) { srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler - dstDb, _ := ethdb.NewMemDatabase() + dstDb := ethdb.NewMemDatabase() sched := NewStateSync(srcRoot, dstDb) queue := append([]common.Hash{}, sched.Missing(batch)...) @@ -173,7 +171,7 @@ func TestIterativeDelayedStateSync(t *testing.T) { srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler - dstDb, _ := ethdb.NewMemDatabase() + dstDb := ethdb.NewMemDatabase() sched := NewStateSync(srcRoot, dstDb) queue := append([]common.Hash{}, sched.Missing(0)...) @@ -210,7 +208,7 @@ func testIterativeRandomStateSync(t *testing.T, batch int) { srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler - dstDb, _ := ethdb.NewMemDatabase() + dstDb := ethdb.NewMemDatabase() sched := NewStateSync(srcRoot, dstDb) queue := make(map[common.Hash]struct{}) @@ -250,7 +248,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { srcDb, srcRoot, srcAccounts := makeTestState() // Create a destination state and sync with the scheduler - dstDb, _ := ethdb.NewMemDatabase() + dstDb := ethdb.NewMemDatabase() sched := NewStateSync(srcRoot, dstDb) queue := make(map[common.Hash]struct{}) @@ -297,7 +295,7 @@ func TestIncompleteStateSync(t *testing.T) { checkTrieConsistency(srcDb.TrieDB().DiskDB().(ethdb.Database), srcRoot) // Create a destination state and sync with the scheduler - dstDb, _ := ethdb.NewMemDatabase() + dstDb := ethdb.NewMemDatabase() sched := NewStateSync(srcRoot, dstDb) added := []common.Hash{} diff --git a/core/state_transition.go b/core/state_transition.go index b19bc12e42..5654cd01ea 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -132,28 +132,12 @@ func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, return NewStateTransition(evm, msg, gp).TransitionDb() } -func (st *StateTransition) from() vm.AccountRef { - f := st.msg.From() - if !st.state.Exist(f) { - st.state.CreateAccount(f) +// to returns the recipient of the message. +func (st *StateTransition) to() common.Address { + if st.msg == nil || st.msg.To() == nil /* contract creation */ { + return common.Address{} } - return vm.AccountRef(f) -} - -func (st *StateTransition) to() vm.AccountRef { - if st.msg == nil { - return vm.AccountRef{} - } - to := st.msg.To() - if to == nil { - return vm.AccountRef{} // contract creation - } - - reference := vm.AccountRef(*to) - if !st.state.Exist(*to) { - st.state.CreateAccount(*to) - } - return reference + return *st.msg.To() } func (st *StateTransition) useGas(amount uint64) error { @@ -166,12 +150,8 @@ func (st *StateTransition) useGas(amount uint64) error { } func (st *StateTransition) buyGas() error { - var ( - state = st.state - sender = st.from() - ) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if state.GetBalance(sender.Address()).Cmp(mgval) < 0 { + if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { return errInsufficientBalanceForGas } if err := st.gp.SubGas(st.msg.Gas()); err != nil { @@ -180,20 +160,17 @@ func (st *StateTransition) buyGas() error { st.gas += st.msg.Gas() st.initialGas = st.msg.Gas() - state.SubBalance(sender.Address(), mgval) + st.state.SubBalance(st.msg.From(), mgval) return nil } func (st *StateTransition) preCheck() error { - msg := st.msg - sender := st.from() - - // Make sure this transaction's nonce is correct - if msg.CheckNonce() { - nonce := st.state.GetNonce(sender.Address()) - if nonce < msg.Nonce() { + // Make sure this transaction's nonce is correct. + if st.msg.CheckNonce() { + nonce := st.state.GetNonce(st.msg.From()) + if nonce < st.msg.Nonce() { return ErrNonceTooHigh - } else if nonce > msg.Nonce() { + } else if nonce > st.msg.Nonce() { return ErrNonceTooLow } } @@ -208,8 +185,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo return } msg := st.msg - sender := st.from() // err checked in preCheck - + sender := vm.AccountRef(msg.From()) homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber) contractCreation := msg.To() == nil @@ -233,8 +209,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) } else { // Increment the nonce for the next transaction - st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1) - ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value) + st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) + ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) } if vmerr != nil { log.Debug("VM returned with error", "err", vmerr) @@ -260,10 +236,8 @@ func (st *StateTransition) refundGas() { st.gas += refund // Return ETH for remaining gas, exchanged at the original rate. - sender := st.from() - remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.state.AddBalance(sender.Address(), remaining) + st.state.AddBalance(st.msg.From(), remaining) // Also return remaining gas to the block gas counter so it is // available for the next transaction. diff --git a/core/tx_list.go b/core/tx_list.go index 55fc42617d..ea6ee7019f 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -367,9 +367,20 @@ func (l *txList) Flatten() types.Transactions { // price-sorted transactions to discard when the pool fills up. type priceHeap []*types.Transaction -func (h priceHeap) Len() int { return len(h) } -func (h priceHeap) Less(i, j int) bool { return h[i].GasPrice().Cmp(h[j].GasPrice()) < 0 } -func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h priceHeap) Len() int { return len(h) } +func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h priceHeap) Less(i, j int) bool { + // Sort primarily by price, returning the cheaper one + switch h[i].GasPrice().Cmp(h[j].GasPrice()) { + case -1: + return true + case 1: + return false + } + // If the prices match, stabilize via nonces (high nonce is worse) + return h[i].Nonce() > h[j].Nonce() +} func (h *priceHeap) Push(x interface{}) { *h = append(*h, x.(*types.Transaction)) diff --git a/core/tx_pool.go b/core/tx_pool.go index 089bd215ad..388b40058f 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -38,8 +38,6 @@ import ( const ( // chainHeadChanSize is the size of channel listening to ChainHeadEvent. chainHeadChanSize = 10 - // rmTxChanSize is the size of channel listening to RemovedTransactionEvent. - rmTxChanSize = 10 ) var ( @@ -320,7 +318,7 @@ func (pool *TxPool) loop() { // Any non-locals old enough should be removed if time.Since(pool.beats[addr]) > pool.config.Lifetime { for _, tx := range pool.queue[addr].Flatten() { - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), true) } } } @@ -468,7 +466,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { pool.gasPrice = price for _, tx := range pool.priced.Cap(price, pool.locals) { - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), false) } log.Info("Transaction pool price threshold updated", "price", price) } @@ -620,7 +618,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { // If the transaction pool is full, discard underpriced transactions if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it - if pool.priced.Underpriced(tx, pool.locals) { + if !local && pool.priced.Underpriced(tx, pool.locals) { log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) underpricedTxCounter.Inc(1) return false, ErrUnderpriced @@ -630,7 +628,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { for _, tx := range drop { log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) underpricedTxCounter.Inc(1) - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), false) } } // If the transaction is replacing an already pending one, do directly @@ -695,8 +693,10 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er pool.priced.Removed() queuedReplaceCounter.Inc(1) } - pool.all[hash] = tx - pool.priced.Put(tx) + if pool.all[hash] == nil { + pool.all[hash] = tx + pool.priced.Put(tx) + } return old != nil, nil } @@ -862,7 +862,7 @@ func (pool *TxPool) Get(hash common.Hash) *types.Transaction { // removeTx removes a single transaction from the queue, moving all subsequent // transactions back to the future queue. -func (pool *TxPool) removeTx(hash common.Hash) { +func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { // Fetch the transaction we wish to delete tx, ok := pool.all[hash] if !ok { @@ -872,8 +872,9 @@ func (pool *TxPool) removeTx(hash common.Hash) { // Remove it from the list of known transactions delete(pool.all, hash) - pool.priced.Removed() - + if outofbound { + pool.priced.Removed() + } // Remove the transaction from the pending lists and reset the account nonce if pending := pool.pending[addr]; pending != nil { if removed, invalids := pending.Remove(tx); removed { @@ -1052,7 +1053,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { for _, tx := range list.Flatten() { - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), true) } drop -= size queuedRateLimitCounter.Inc(int64(size)) @@ -1061,7 +1062,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) { // Otherwise drop only last few transactions txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - pool.removeTx(txs[i].Hash()) + pool.removeTx(txs[i].Hash(), true) drop-- queuedRateLimitCounter.Inc(1) } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 1cf533aa65..e7f52075e5 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -78,8 +78,7 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec } func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { - diskdb, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(diskdb)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} key, _ := crypto.GenerateKey() @@ -158,8 +157,7 @@ func (c *testChain) State() (*state.StateDB, error) { // a state change between those fetches. stdb := c.statedb if *c.trigger { - db, _ := ethdb.NewMemDatabase() - c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(db)) + c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) // simulate that the new head block included tx0 and tx1 c.statedb.SetNonce(c.address, 2) c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) @@ -175,10 +173,9 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { t.Parallel() var ( - db, _ = ethdb.NewMemDatabase() key, _ = crypto.GenerateKey() address = crypto.PubkeyToAddress(key.PublicKey) - statedb, _ = state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) trigger = false ) @@ -209,15 +206,10 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { pool.lockedReset(nil, nil) - pendingTx, err := pool.Pending() + _, err := pool.Pending() if err != nil { t.Fatalf("Could not fetch pending transactions: %v", err) } - - for addr, txs := range pendingTx { - t.Logf("%0x: %d\n", addr, len(txs)) - } - nonce = pool.State().GetNonce(address) if nonce != 2 { t.Fatalf("Invalid nonce, want 2, got %d", nonce) @@ -337,8 +329,7 @@ func TestTransactionChainFork(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) statedb.AddBalance(addr, big.NewInt(100000000000000)) pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} @@ -350,7 +341,7 @@ func TestTransactionChainFork(t *testing.T) { if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), true) // reset the pool's internal state resetState() @@ -367,8 +358,7 @@ func TestTransactionDoubleNonce(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) statedb.AddBalance(addr, big.NewInt(100000000000000)) pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} @@ -558,8 +548,7 @@ func TestTransactionPostponing(t *testing.T) { t.Parallel() // Create the pool to test the postponing with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -774,8 +763,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { t.Parallel() // Create the pool to test the limit enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -863,8 +851,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { evictionInterval = time.Second // Create the pool to test the non-expiration enforcement - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1018,8 +1005,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1065,8 +1051,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1100,8 +1085,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1149,8 +1133,7 @@ func TestTransactionPoolRepricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1271,8 +1254,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1334,8 +1316,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1351,7 +1332,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) { defer sub.Unsubscribe() // Create a number of test accounts and fund them - keys := make([]*ecdsa.PrivateKey, 3) + keys := make([]*ecdsa.PrivateKey, 4) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) @@ -1388,13 +1369,13 @@ func TestTransactionPoolUnderpricing(t *testing.T) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } // Ensure that adding high priced transactions drops cheap ones, but not own - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - t.Fatalf("failed to add well priced transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 t.Fatalf("failed to add well priced transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { + if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } pending, queued = pool.Stats() @@ -1404,25 +1385,29 @@ func TestTransactionPoolUnderpricing(t *testing.T) { if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validateEvents(events, 2); err != nil { - t.Fatalf("additional event firing failed: %v", err) - } - if err := validateTxPoolInternals(pool); err != nil { - t.Fatalf("pool internal state corrupted: %v", err) - } - // Ensure that adding local transactions can push out even higher priced ones - tx := pricedTransaction(1, 100000, big.NewInt(0), keys[2]) - if err := pool.AddLocal(tx); err != nil { - t.Fatalf("failed to add underpriced local transaction: %v", err) - } - pending, queued = pool.Stats() - if pending != 2 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) - } - if queued != 2 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) - } if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding local transactions can push out even higher priced ones + ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2]) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to append underpriced local transaction: %v", err) + } + ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) + } + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) + } + if err := validateEvents(events, 2); err != nil { t.Fatalf("local event firing failed: %v", err) } if err := validateTxPoolInternals(pool); err != nil { @@ -1430,14 +1415,80 @@ func TestTransactionPoolUnderpricing(t *testing.T) { } } +// Tests that more expensive transactions push out cheap ones from the pool, but +// without producing instability by creating gaps that start jumping transactions +// back and forth between queued/pending. +func TestTransactionPoolStableUnderpricing(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + + config := testTxPoolConfig + config.GlobalSlots = 128 + config.GlobalQueue = 0 + + pool := NewTxPool(config, params.TestChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan TxPreEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 2) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Fill up the entire queue with the same transaction price points + txs := types.Transactions{} + for i := uint64(0); i < config.GlobalSlots; i++ { + txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0])) + } + pool.AddRemotes(txs) + + pending, queued := pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, int(config.GlobalSlots)); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that the pool rejects replacement transactions that don't meet the minimum // price bump required. func TestTransactionReplacement(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1531,8 +1582,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { os.Remove(journal) // Create the original pool to inject transaction into the journal - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1630,8 +1680,7 @@ func TestTransactionStatusCheck(t *testing.T) { t.Parallel() // Create the pool to test the status retrievals with - db, _ := ethdb.NewMemDatabase() - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) diff --git a/core/types/transaction.go b/core/types/transaction.go index 70d757c94e..c1cb7a043a 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -33,7 +33,6 @@ import ( var ( ErrInvalidSig = errors.New("invalid transaction v, r, s values") - errNoSigner = errors.New("missing signing methods") ) // deriveSigner makes a *best* guess about which signer to use. @@ -340,11 +339,14 @@ type TransactionsByPriceAndNonce struct { func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce { // Initialize a price based heap with the head transactions heads := make(TxByPrice, 0, len(txs)) - for _, accTxs := range txs { + for from, accTxs := range txs { heads = append(heads, accTxs[0]) // Ensure the sender address is from the signer acc, _ := Sender(signer, accTxs[0]) txs[acc] = accTxs[1:] + if from != acc { + delete(txs, from) + } } heap.Init(&heads) diff --git a/core/vm/contract.go b/core/vm/contract.go index 66748e8215..b466681dbd 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -139,15 +139,15 @@ func (c *Contract) Value() *big.Int { } // SetCode sets the code to the contract -func (self *Contract) SetCode(hash common.Hash, code []byte) { - self.Code = code - self.CodeHash = hash +func (c *Contract) SetCode(hash common.Hash, code []byte) { + c.Code = code + c.CodeHash = hash } // SetCallCode sets the code of the contract and address of the backing data // object -func (self *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { - self.Code = code - self.CodeHash = hash - self.CodeAddr = addr +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { + c.Code = code + c.CodeHash = hash + c.CodeAddr = addr } diff --git a/core/vm/evm.go b/core/vm/evm.go index 96676c314a..ea46209742 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -160,6 +160,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas precompiles = PrecompiledContractsByzantium } if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 { + // Calling a non existing account, don't do antything, but ping the tracer + if evm.vmConfig.Debug && evm.depth == 0 { + evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) + evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) + } return nil, gas, nil } evm.StateDB.CreateAccount(addr) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 1e494a0eb8..0689ee39cf 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -31,7 +31,6 @@ import ( var ( bigZero = new(big.Int) tt255 = math.BigPow(2, 255) - tt256 = math.BigPow(2, 256) errWriteProtection = errors.New("evm: write protection") errReturnDataOutOfBounds = errors.New("evm: return data out of bounds") errExecutionReverted = errors.New("evm: execution reverted") diff --git a/core/vm/logger.go b/core/vm/logger.go index dde1903bf2..c32a7b4044 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -31,9 +31,9 @@ import ( type Storage map[common.Hash]common.Hash -func (self Storage) Copy() Storage { +func (s Storage) Copy() Storage { cpy := make(Storage) - for key, value := range self { + for key, value := range s { cpy[key] = value } diff --git a/core/vm/memory.go b/core/vm/memory.go index 99a84d2271..d5cc7870bc 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -51,14 +51,14 @@ func (m *Memory) Resize(size uint64) { } // Get returns offset + size as a new slice -func (self *Memory) Get(offset, size int64) (cpy []byte) { +func (m *Memory) Get(offset, size int64) (cpy []byte) { if size == 0 { return nil } - if len(self.store) > int(offset) { + if len(m.store) > int(offset) { cpy = make([]byte, size) - copy(cpy, self.store[offset:offset+size]) + copy(cpy, m.store[offset:offset+size]) return } @@ -67,13 +67,13 @@ func (self *Memory) Get(offset, size int64) (cpy []byte) { } // GetPtr returns the offset + size -func (self *Memory) GetPtr(offset, size int64) []byte { +func (m *Memory) GetPtr(offset, size int64) []byte { if size == 0 { return nil } - if len(self.store) > int(offset) { - return self.store[offset : offset+size] + if len(m.store) > int(offset) { + return m.store[offset : offset+size] } return nil diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 7fe55b72f6..e3568eb000 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -375,10 +375,10 @@ var opCodeToString = map[OpCode]string{ SWAP: "SWAP", } -func (o OpCode) String() string { - str := opCodeToString[o] +func (op OpCode) String() string { + str := opCodeToString[op] if len(str) == 0 { - return fmt.Sprintf("Missing opcode 0x%x", int(o)) + return fmt.Sprintf("Missing opcode 0x%x", int(op)) } return str diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 1e9ed7ae2d..c8c5d34d9d 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -99,11 +99,10 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { setDefaults(cfg) if cfg.State == nil { - db, _ := ethdb.NewMemDatabase() - cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db)) + cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) } var ( - address = common.StringToAddress("contract") + address = common.BytesToAddress([]byte("contract")) vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) @@ -113,7 +112,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // Call the code with the given configuration. ret, _, err := vmenv.Call( sender, - common.StringToAddress("contract"), + common.BytesToAddress([]byte("contract")), input, cfg.GasLimit, cfg.Value, @@ -130,8 +129,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { setDefaults(cfg) if cfg.State == nil { - db, _ := ethdb.NewMemDatabase() - cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db)) + cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) } var ( vmenv = NewEnv(cfg) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 2c4dc50265..ef664bda30 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -94,8 +94,7 @@ func TestExecute(t *testing.T) { } func TestCall(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - state, _ := state.New(common.Hash{}, state.NewDatabase(db)) + state, _ := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) address := common.HexToAddress("0x0a") state.SetCode(address, []byte{ byte(vm.PUSH1), 10, diff --git a/crypto/bn256/cloudflare/example_test.go b/crypto/bn256/cloudflare/example_test.go index b2d19807a2..6c285995cb 100644 --- a/crypto/bn256/cloudflare/example_test.go +++ b/crypto/bn256/cloudflare/example_test.go @@ -6,9 +6,12 @@ package bn256 import ( "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" ) -func ExamplePair() { +func TestExamplePair(t *testing.T) { // This implements the tripartite Diffie-Hellman algorithm from "A One // Round Protocol for Tripartite Diffie-Hellman", A. Joux. // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf @@ -40,4 +43,9 @@ func ExamplePair() { k3.ScalarMult(k3, c) // k1, k2 and k3 will all be equal. + + require.Equal(t, k1, k2) + require.Equal(t, k1, k3) + + require.Equal(t, len(np), 4) //Avoid gometalinter varcheck err on np } diff --git a/crypto/crypto.go b/crypto/crypto.go index 1c4d5a2e02..76d1ffaf64 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -35,8 +35,8 @@ import ( ) var ( - secp256k1_N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) - secp256k1_halfN = new(big.Int).Div(secp256k1_N, big.NewInt(2)) + secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) + secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) ) // Keccak256 calculates and returns the Keccak256 hash of the input data. @@ -68,7 +68,7 @@ func Keccak512(data ...[]byte) []byte { return d.Sum(nil) } -// Creates an ethereum address given the bytes and the nonce +// CreateAddress creates an ethereum address given the bytes and the nonce func CreateAddress(b common.Address, nonce uint64) common.Address { data, _ := rlp.EncodeToBytes([]interface{}{b, nonce}) return common.BytesToAddress(Keccak256(data)[12:]) @@ -99,7 +99,7 @@ func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) { priv.D = new(big.Int).SetBytes(d) // The priv.D must < N - if priv.D.Cmp(secp256k1_N) >= 0 { + if priv.D.Cmp(secp256k1N) >= 0 { return nil, fmt.Errorf("invalid private key, >=N") } // The priv.D must not be zero or negative. @@ -184,11 +184,11 @@ func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool { } // reject upper range of s values (ECDSA malleability) // see discussion in secp256k1/libsecp256k1/include/secp256k1.h - if homestead && s.Cmp(secp256k1_halfN) > 0 { + if homestead && s.Cmp(secp256k1halfN) > 0 { return false } // Frontier: allow s to be in full N range - return r.Cmp(secp256k1_N) < 0 && s.Cmp(secp256k1_N) < 0 && (v == 0 || v == 1) + return r.Cmp(secp256k1N) < 0 && s.Cmp(secp256k1N) < 0 && (v == 0 || v == 1) } func PubkeyToAddress(p ecdsa.PublicKey) common.Address { diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 8350354623..804de3fe22 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -154,7 +154,7 @@ func TestValidateSignatureValues(t *testing.T) { minusOne := big.NewInt(-1) one := common.Big1 zero := common.Big0 - secp256k1nMinus1 := new(big.Int).Sub(secp256k1_N, common.Big1) + secp256k1nMinus1 := new(big.Int).Sub(secp256k1N, common.Big1) // correct v,r,s check(true, 0, one, one) @@ -181,9 +181,9 @@ func TestValidateSignatureValues(t *testing.T) { // correct sig with max r,s check(true, 0, secp256k1nMinus1, secp256k1nMinus1) // correct v, combinations of incorrect r,s at upper limit - check(false, 0, secp256k1_N, secp256k1nMinus1) - check(false, 0, secp256k1nMinus1, secp256k1_N) - check(false, 0, secp256k1_N, secp256k1_N) + check(false, 0, secp256k1N, secp256k1nMinus1) + check(false, 0, secp256k1nMinus1, secp256k1N) + check(false, 0, secp256k1N, secp256k1N) // current callers ensures r,s cannot be negative, but let's test for that too // as crypto package could be used stand-alone diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index f51be5e353..6fdf2be6a4 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -77,7 +77,7 @@ func (BitCurve *BitCurve) Params() *elliptic.CurveParams { } } -// IsOnBitCurve returns true if the given (x,y) lies on the BitCurve. +// IsOnCurve returns true if the given (x,y) lies on the BitCurve. func (BitCurve *BitCurve) IsOnCurve(x, y *big.Int) bool { // y² = x³ + b y2 := new(big.Int).Mul(y, y) //y² diff --git a/crypto/secp256k1/secp256_test.go b/crypto/secp256k1/secp256_test.go index f6582ecd54..b608bcfcf1 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -49,7 +49,7 @@ func randSig() []byte { // tests for malleability // highest bit of signature ECDSA s value must be 0, in the 33th byte func compactSigCheck(t *testing.T, sig []byte) { - var b int = int(sig[32]) + var b = int(sig[32]) if b < 0 { t.Errorf("highest bit is negative: %d", b) } diff --git a/crypto/signature_nocgo.go b/crypto/signature_nocgo.go index f636b23772..e8fa18ed47 100644 --- a/crypto/signature_nocgo.go +++ b/crypto/signature_nocgo.go @@ -88,7 +88,7 @@ func VerifySignature(pubkey, hash, signature []byte) bool { return false } // Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. - if sig.S.Cmp(secp256k1_halfN) > 0 { + if sig.S.Cmp(secp256k1halfN) > 0 { return false } return sig.Verify(hash, key) diff --git a/eth/api.go b/eth/api.go index a345b57e49..247ca7485c 100644 --- a/eth/api.go +++ b/eth/api.go @@ -19,6 +19,7 @@ package eth import ( "compress/gzip" "context" + "errors" "fmt" "io" "math/big" @@ -28,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -343,8 +345,10 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug // Preimage is a debug API function that returns the preimage for a sha3 hash, if known. func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { - db := core.PreimageTable(api.eth.ChainDb()) - return db.Get(hash.Bytes()) + if preimage := rawdb.ReadPreimage(api.eth.ChainDb(), hash); preimage != nil { + return preimage, nil + } + return nil, errors.New("unknown preimage") } // GetBadBLocks returns a list of the last 'bad blocks' that the client has seen on the network diff --git a/eth/api_backend.go b/eth/api_backend.go index ecd5488a24..4ace9b5945 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -36,26 +37,26 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// EthApiBackend implements ethapi.Backend for full nodes -type EthApiBackend struct { +// EthAPIBackend implements ethapi.Backend for full nodes +type EthAPIBackend struct { eth *Ethereum gpo *gasprice.Oracle } -func (b *EthApiBackend) ChainConfig() *params.ChainConfig { +func (b *EthAPIBackend) ChainConfig() *params.ChainConfig { return b.eth.chainConfig } -func (b *EthApiBackend) CurrentBlock() *types.Block { +func (b *EthAPIBackend) CurrentBlock() *types.Block { return b.eth.blockchain.CurrentBlock() } -func (b *EthApiBackend) SetHead(number uint64) { +func (b *EthAPIBackend) SetHead(number uint64) { b.eth.protocolManager.downloader.Cancel() b.eth.blockchain.SetHead(number) } -func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { +func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { // Pending block is only known by the miner if blockNr == rpc.PendingBlockNumber { block := b.eth.miner.PendingBlock() @@ -68,7 +69,7 @@ func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum return b.eth.blockchain.GetHeaderByNumber(uint64(blockNr)), nil } -func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) { +func (b *EthAPIBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) { // Pending block is only known by the miner if blockNr == rpc.PendingBlockNumber { block := b.eth.miner.PendingBlock() @@ -81,7 +82,7 @@ func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumb return b.eth.blockchain.GetBlockByNumber(uint64(blockNr)), nil } -func (b *EthApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) { +func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) { // Pending state is only known by the miner if blockNr == rpc.PendingBlockNumber { block, state := b.eth.miner.Pending() @@ -96,16 +97,23 @@ func (b *EthApiBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc. return stateDb, header, err } -func (b *EthApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*types.Block, error) { - return b.eth.blockchain.GetBlockByHash(blockHash), nil +func (b *EthAPIBackend) GetBlock(ctx context.Context, hash common.Hash) (*types.Block, error) { + return b.eth.blockchain.GetBlockByHash(hash), nil } -func (b *EthApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { - return core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)), nil +func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { + return rawdb.ReadReceipts(b.eth.chainDb, hash, *number), nil + } + return nil, nil } -func (b *EthApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) { - receipts := core.GetBlockReceipts(b.eth.chainDb, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)) +func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { + number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash) + if number == nil { + return nil, nil + } + receipts := rawdb.ReadReceipts(b.eth.chainDb, hash, *number) if receipts == nil { return nil, nil } @@ -116,11 +124,11 @@ func (b *EthApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][ return logs, nil } -func (b *EthApiBackend) GetTd(blockHash common.Hash) *big.Int { +func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { return b.eth.blockchain.GetTdByHash(blockHash) } -func (b *EthApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { state.SetBalance(msg.From(), math.MaxBig256) vmError := func() error { return nil } @@ -128,31 +136,31 @@ func (b *EthApiBackend) GetEVM(ctx context.Context, msg core.Message, state *sta return vm.NewEVM(context, state, b.eth.chainConfig, vmCfg), vmError, nil } -func (b *EthApiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { +func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { return b.eth.BlockChain().SubscribeRemovedLogsEvent(ch) } -func (b *EthApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { +func (b *EthAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { return b.eth.BlockChain().SubscribeChainEvent(ch) } -func (b *EthApiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { +func (b *EthAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { return b.eth.BlockChain().SubscribeChainHeadEvent(ch) } -func (b *EthApiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { +func (b *EthAPIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { return b.eth.BlockChain().SubscribeChainSideEvent(ch) } -func (b *EthApiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { +func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { return b.eth.BlockChain().SubscribeLogsEvent(ch) } -func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { +func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { return b.eth.txPool.AddLocal(signedTx) } -func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) { +func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending, err := b.eth.txPool.Pending() if err != nil { return nil, err @@ -164,56 +172,56 @@ func (b *EthApiBackend) GetPoolTransactions() (types.Transactions, error) { return txs, nil } -func (b *EthApiBackend) GetPoolTransaction(hash common.Hash) *types.Transaction { +func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction { return b.eth.txPool.Get(hash) } -func (b *EthApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { +func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { return b.eth.txPool.State().GetNonce(addr), nil } -func (b *EthApiBackend) Stats() (pending int, queued int) { +func (b *EthAPIBackend) Stats() (pending int, queued int) { return b.eth.txPool.Stats() } -func (b *EthApiBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { +func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { return b.eth.TxPool().Content() } -func (b *EthApiBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { +func (b *EthAPIBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription { return b.eth.TxPool().SubscribeTxPreEvent(ch) } -func (b *EthApiBackend) Downloader() *downloader.Downloader { +func (b *EthAPIBackend) Downloader() *downloader.Downloader { return b.eth.Downloader() } -func (b *EthApiBackend) ProtocolVersion() int { +func (b *EthAPIBackend) ProtocolVersion() int { return b.eth.EthVersion() } -func (b *EthApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { +func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { return b.gpo.SuggestPrice(ctx) } -func (b *EthApiBackend) ChainDb() ethdb.Database { +func (b *EthAPIBackend) ChainDb() ethdb.Database { return b.eth.ChainDb() } -func (b *EthApiBackend) EventMux() *event.TypeMux { +func (b *EthAPIBackend) EventMux() *event.TypeMux { return b.eth.EventMux() } -func (b *EthApiBackend) AccountManager() *accounts.Manager { +func (b *EthAPIBackend) AccountManager() *accounts.Manager { return b.eth.AccountManager() } -func (b *EthApiBackend) BloomStatus() (uint64, uint64) { +func (b *EthAPIBackend) BloomStatus() (uint64, uint64) { sections, _, _ := b.eth.bloomIndexer.Sections() return params.BloomBitsBlocks, sections } -func (b *EthApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { +func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { for i := 0; i < bloomFilterThreads; i++ { go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) } diff --git a/eth/api_test.go b/eth/api_test.go index 900a82bb6a..47b062a40c 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -31,8 +31,7 @@ var dumper = spew.ConfigState{Indent: " "} func TestStorageRangeAt(t *testing.T) { // Create a state where account 0x010000... has a few storage entries. var ( - db, _ = ethdb.NewMemDatabase() - state, _ = state.New(common.Hash{}, state.NewDatabase(db)) + state, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase())) addr = common.Address{0x01} keys = []common.Hash{ // hashes of Keys of storage common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"), diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 07c4457bc3..45a819022a 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -201,7 +202,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) break } - task.statedb.DeleteSuicides() + task.statedb.Finalise(true) task.results[i] = &txTraceResult{Result: res} } // Stream the result back to the user or abort on teardown @@ -533,7 +534,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* // and returns them as a JSON object. func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { // Retrieve the transaction and assemble its EVM context - tx, blockHash, _, index := core.GetTransaction(api.eth.ChainDb(), hash) + tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash) if tx == nil { return nil, fmt.Errorf("transaction %x not found", hash) } @@ -640,7 +641,8 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) } - statedb.DeleteSuicides() + // Ensure any modifications are committed to the state + statedb.Finalise(true) } return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash) } diff --git a/eth/backend.go b/eth/backend.go index ffd5d85421..ea70e3826c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" @@ -63,8 +64,7 @@ type Ethereum struct { chainConfig *params.ChainConfig // Channel for shutting down the service - shutdownChan chan bool // Channel for shutting down the Ethereum - stopDbUpgrade func() error // stop chain db sequential key upgrade + shutdownChan chan bool // Channel for shutting down the Ethereum // Handlers txPool *core.TxPool @@ -82,7 +82,7 @@ type Ethereum struct { bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports - ApiBackend *EthApiBackend + APIBackend *EthAPIBackend miner *miner.Miner gasPrice *big.Int @@ -112,7 +112,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if err != nil { return nil, err } - stopDbUpgrade := upgradeDeduplicateData(chainDb) chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, config.Genesis) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr @@ -127,7 +126,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { accountManager: ctx.AccountManager, engine: CreateConsensusEngine(ctx, &config.Ethash, chainConfig, chainDb), shutdownChan: make(chan bool), - stopDbUpgrade: stopDbUpgrade, networkId: config.NetworkId, gasPrice: config.GasPrice, etherbase: config.Etherbase, @@ -138,11 +136,11 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId) if !config.SkipBcVersionCheck { - bcVersion := core.GetBlockChainVersion(chainDb) + bcVersion := rawdb.ReadDatabaseVersion(chainDb) if bcVersion != core.BlockChainVersion && bcVersion != 0 { return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, core.BlockChainVersion) } - core.WriteBlockChainVersion(chainDb, core.BlockChainVersion) + rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) } var ( vmConfig = vm.Config{EnablePreimageRecording: config.EnablePreimageRecording} @@ -156,7 +154,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) eth.blockchain.SetHead(compat.RewindTo) - core.WriteChainConfig(chainDb, genesisHash, chainConfig) + rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } eth.bloomIndexer.Start(eth.blockchain) @@ -171,12 +169,12 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine) eth.miner.SetExtra(makeExtraData(config.ExtraData)) - eth.ApiBackend = &EthApiBackend{eth, nil} + eth.APIBackend = &EthAPIBackend{eth, nil} gpoParams := config.GPO if gpoParams.Default == nil { gpoParams.Default = config.GasPrice } - eth.ApiBackend.gpo = gasprice.NewOracle(eth.ApiBackend, gpoParams) + eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams) return eth, nil } @@ -244,7 +242,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai // APIs returns the collection of RPC services the ethereum package offers. // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { - apis := ethapi.GetAPIs(s.ApiBackend) + apis := ethapi.GetAPIs(s.APIBackend) // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) @@ -274,7 +272,7 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: filters.NewPublicFilterAPI(s.ApiBackend, false), + Service: filters.NewPublicFilterAPI(s.APIBackend, false), Public: true, }, { Namespace: "admin", @@ -325,13 +323,13 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) { return common.Address{}, fmt.Errorf("etherbase must be explicitly specified") } -// set in js console via admin interface or wrapper from cli flags -func (self *Ethereum) SetEtherbase(etherbase common.Address) { - self.lock.Lock() - self.etherbase = etherbase - self.lock.Unlock() +// SetEtherbase sets the mining reward address. +func (s *Ethereum) SetEtherbase(etherbase common.Address) { + s.lock.Lock() + s.etherbase = etherbase + s.lock.Unlock() - self.miner.SetEtherbase(etherbase) + s.miner.SetEtherbase(etherbase) } func (s *Ethereum) StartMining(local bool) error { @@ -411,9 +409,6 @@ func (s *Ethereum) Start(srvr *p2p.Server) error { // Stop implements node.Service, terminating all internal goroutines used by the // Ethereum protocol. func (s *Ethereum) Stop() error { - if s.stopDbUpgrade != nil { - s.stopDbUpgrade() - } s.bloomIndexer.Close() s.blockchain.Stop() s.protocolManager.Stop() diff --git a/eth/bloombits.go b/eth/bloombits.go index c5597391c5..954239d141 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" @@ -60,8 +61,8 @@ func (eth *Ethereum) startBloomHandlers() { task := <-request task.Bitsets = make([][]byte, len(task.Sections)) for i, section := range task.Sections { - head := core.GetCanonicalHash(eth.chainDb, (section+1)*params.BloomBitsBlocks-1) - if compVector, err := core.GetBloomBits(eth.chainDb, task.Bit, section, head); err == nil { + head := rawdb.ReadCanonicalHash(eth.chainDb, (section+1)*params.BloomBitsBlocks-1) + if compVector, err := rawdb.ReadBloomBits(eth.chainDb, task.Bit, section, head); err == nil { if blob, err := bitutil.DecompressBytes(compVector, int(params.BloomBitsBlocks)/8); err == nil { task.Bitsets[i] = blob } else { @@ -107,7 +108,7 @@ func NewBloomIndexer(db ethdb.Database, size uint64) *core.ChainIndexer { db: db, size: size, } - table := ethdb.NewTable(db, string(core.BloomBitsIndexPrefix)) + table := ethdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix)) return core.NewChainIndexer(db, table, backend, size, bloomConfirms, bloomThrottling, "bloombits") } @@ -137,7 +138,7 @@ func (b *BloomIndexer) Commit() error { if err != nil { return err } - core.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) + rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits)) } return batch.Write() } diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go deleted file mode 100644 index 96c584ac64..0000000000 --- a/eth/db_upgrade.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2016 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 . - -// Package eth implements the Ethereum protocol. -package eth - -import ( - "bytes" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" -) - -var deduplicateData = []byte("dbUpgrade_20170714deduplicateData") - -// upgradeDeduplicateData checks the chain database version and -// starts a background process to make upgrades if necessary. -// Returns a stop function that blocks until the process has -// been safely stopped. -func upgradeDeduplicateData(db ethdb.Database) func() error { - // If the database is already converted or empty, bail out - data, _ := db.Get(deduplicateData) - if len(data) > 0 && data[0] == 42 { - return nil - } - if data, _ := db.Get([]byte("LastHeader")); len(data) == 0 { - db.Put(deduplicateData, []byte{42}) - return nil - } - // Start the deduplication upgrade on a new goroutine - log.Warn("Upgrading database to use lookup entries") - stop := make(chan chan error) - - go func() { - // Create an iterator to read the entire database and covert old lookup entires - it := db.(*ethdb.LDBDatabase).NewIterator() - defer func() { - if it != nil { - it.Release() - } - }() - - var ( - converted uint64 - failed error - ) - for failed == nil && it.Next() { - // Skip any entries that don't look like old transaction meta entries (0x01) - key := it.Key() - if len(key) != common.HashLength+1 || key[common.HashLength] != 0x01 { - continue - } - // Skip any entries that don't contain metadata (name clash between 0x01 and ) - var meta struct { - BlockHash common.Hash - BlockIndex uint64 - Index uint64 - } - if err := rlp.DecodeBytes(it.Value(), &meta); err != nil { - continue - } - // Skip any already upgraded entries (clash due to ending with 0x01 (old suffix)) - hash := key[:common.HashLength] - - if hash[0] == byte('l') { - // Potential clash, the "old" `hash` must point to a live transaction. - if tx, _, _, _ := core.GetTransaction(db, common.BytesToHash(hash)); tx == nil || !bytes.Equal(tx.Hash().Bytes(), hash) { - continue - } - } - // Convert the old metadata to a new lookup entry, delete duplicate data - if failed = db.Put(append([]byte("l"), hash...), it.Value()); failed == nil { // Write the new lookup entry - if failed = db.Delete(hash); failed == nil { // Delete the duplicate transaction data - if failed = db.Delete(append([]byte("receipts-"), hash...)); failed == nil { // Delete the duplicate receipt data - if failed = db.Delete(key); failed != nil { // Delete the old transaction metadata - break - } - } - } - } - // Bump the conversion counter, and recreate the iterator occasionally to - // avoid too high memory consumption. - converted++ - if converted%100000 == 0 { - it.Release() - it = db.(*ethdb.LDBDatabase).NewIterator() - it.Seek(key) - - log.Info("Deduplicating database entries", "deduped", converted) - } - // Check for termination, or continue after a bit of a timeout - select { - case errc := <-stop: - errc <- nil - return - case <-time.After(time.Microsecond * 100): - } - } - // Upgrade finished, mark a such and terminate - if failed == nil { - log.Info("Database deduplication successful", "deduped", converted) - db.Put(deduplicateData, []byte{42}) - } else { - log.Error("Database deduplication failed", "deduped", converted, "err", failed) - } - it.Release() - it = nil - - errc := <-stop - errc <- failed - }() - // Assembly the cancellation callback - return func() error { - errc := make(chan error) - stop <- errc - return <-errc - } -} diff --git a/eth/downloader/api.go b/eth/downloader/api.go index d496fa6a4d..91c6322d41 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -51,7 +51,7 @@ func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAP return api } -// eventLoop runs an loop until the event mux closes. It will install and uninstall new +// eventLoop runs a loop until the event mux closes. It will install and uninstall new // sync subscriptions and broadcasts sync status updates to the installed sync subscriptions. func (api *PublicDownloaderAPI) eventLoop() { var ( diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 9e49498998..dc23354929 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -27,7 +27,7 @@ import ( ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -135,9 +135,10 @@ type Downloader struct { stateCh chan dataPack // [eth/63] Channel receiving inbound node state data // Cancellation and termination - cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) - cancelCh chan struct{} // Channel to cancel mid-flight syncs - cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers + cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) + cancelCh chan struct{} // Channel to cancel mid-flight syncs + cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers + cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. quitCh chan struct{} // Quit channel to signal termination quitLock sync.RWMutex // Lock to prevent double closes @@ -223,7 +224,7 @@ func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockC stateCh: make(chan dataPack), stateSyncStart: make(chan *stateSync), syncStatsState: stateSyncStats{ - processed: core.GetTrieSyncProgress(stateDb), + processed: rawdb.ReadFastTrieProgress(stateDb), }, trackStateReq: make(chan *stateReq), } @@ -305,7 +306,7 @@ func (d *Downloader) UnregisterPeer(id string) error { d.cancelLock.RUnlock() if master { - d.Cancel() + d.cancel() } return nil } @@ -476,12 +477,11 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I // spawnSync runs d.process and all given fetcher functions to completion in // separate goroutines, returning the first error that appears. func (d *Downloader) spawnSync(fetchers []func() error) error { - var wg sync.WaitGroup errc := make(chan error, len(fetchers)) - wg.Add(len(fetchers)) + d.cancelWg.Add(len(fetchers)) for _, fn := range fetchers { fn := fn - go func() { defer wg.Done(); errc <- fn() }() + go func() { defer d.cancelWg.Done(); errc <- fn() }() } // Wait for the first error, then terminate the others. var err error @@ -498,13 +498,13 @@ func (d *Downloader) spawnSync(fetchers []func() error) error { } d.queue.Close() d.Cancel() - wg.Wait() return err } -// Cancel cancels all of the operations and resets the queue. It returns true -// if the cancel operation was completed. -func (d *Downloader) Cancel() { +// cancel aborts all of the operations and resets the queue. However, cancel does +// not wait for the running download goroutines to finish. This method should be +// used when cancelling the downloads from inside the downloader. +func (d *Downloader) cancel() { // Close the current cancel channel d.cancelLock.Lock() if d.cancelCh != nil { @@ -518,6 +518,13 @@ func (d *Downloader) Cancel() { d.cancelLock.Unlock() } +// Cancel aborts all of the operations and waits for all download goroutines to +// finish before returning. +func (d *Downloader) Cancel() { + d.cancel() + d.cancelWg.Wait() +} + // Terminate interrupts the downloader, canceling all pending operations. // The downloader cannot be reused after calling Terminate. func (d *Downloader) Terminate() { diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index e85e234c0e..d1a9a8694b 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -75,7 +75,7 @@ type downloadTester struct { // newTester creates a new downloader test mocker. func newTester() *downloadTester { - testdb, _ := ethdb.NewMemDatabase() + testdb := ethdb.NewMemDatabase() genesis := core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) tester := &downloadTester{ @@ -93,7 +93,7 @@ func newTester() *downloadTester { peerChainTds: make(map[string]map[common.Hash]*big.Int), peerMissingStates: make(map[string]map[common.Hash]bool), } - tester.stateDb, _ = ethdb.NewMemDatabase() + tester.stateDb = ethdb.NewMemDatabase() tester.stateDb.Put(genesis.Root().Bytes(), []byte{0x00}) tester.downloader = New(FullSync, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer) diff --git a/eth/downloader/fakepeer.go b/eth/downloader/fakepeer.go index 5248e7fb0c..59832facaa 100644 --- a/eth/downloader/fakepeer.go +++ b/eth/downloader/fakepeer.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" ) @@ -126,7 +127,7 @@ func (p *FakePeer) RequestBodies(hashes []common.Hash) error { uncles [][]*types.Header ) for _, hash := range hashes { - block := core.GetBlock(p.db, hash, p.hc.GetBlockNumber(hash)) + block := rawdb.ReadBlock(p.db, hash, *p.hc.GetBlockNumber(hash)) txs = append(txs, block.Transactions()) uncles = append(uncles, block.Uncles()) @@ -140,7 +141,7 @@ func (p *FakePeer) RequestBodies(hashes []common.Hash) error { func (p *FakePeer) RequestReceipts(hashes []common.Hash) error { var receipts [][]*types.Receipt for _, hash := range hashes { - receipts = append(receipts, core.GetBlockReceipts(p.db, hash, p.hc.GetBlockNumber(hash))) + receipts = append(receipts, rawdb.ReadReceipts(p.db, hash, *p.hc.GetBlockNumber(hash))) } p.dl.DeliverReceipts(p.id, receipts) return nil diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index 4071d0ad98..5b4b9ba1b7 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -23,7 +23,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/ethdb" @@ -274,15 +274,21 @@ func (s *stateSync) Cancel() error { // receive data from peers, rather those are buffered up in the downloader and // pushed here async. The reason is to decouple processing from data receipt // and timeouts. -func (s *stateSync) loop() error { +func (s *stateSync) loop() (err error) { // Listen for new peer events to assign tasks to them newPeer := make(chan *peerConnection, 1024) peerSub := s.d.peers.SubscribeNewPeers(newPeer) defer peerSub.Unsubscribe() + defer func() { + cerr := s.commit(true) + if err == nil { + err = cerr + } + }() // Keep assigning new tasks until the sync completes or aborts for s.sched.Pending() > 0 { - if err := s.commit(false); err != nil { + if err = s.commit(false); err != nil { return err } s.assignTasks() @@ -307,14 +313,14 @@ func (s *stateSync) loop() error { s.d.dropPeer(req.peer.id) } // Process all the received blobs and check for stale delivery - if err := s.process(req); err != nil { + if err = s.process(req); err != nil { log.Warn("Node data write error", "err", err) return err } req.peer.SetNodeDataIdle(len(req.response)) } } - return s.commit(true) + return nil } func (s *stateSync) commit(force bool) error { @@ -323,7 +329,9 @@ func (s *stateSync) commit(force bool) error { } start := time.Now() b := s.d.stateDB.NewBatch() - s.sched.Commit(b) + if written, err := s.sched.Commit(b); written == 0 || err != nil { + return err + } if err := b.Write(); err != nil { return fmt.Errorf("DB write error: %v", err) } @@ -468,6 +476,6 @@ func (s *stateSync) updateStats(written, duplicate, unexpected int, duration tim log.Info("Imported new state entries", "count", written, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "retry", len(s.tasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected) } if written > 0 { - core.WriteTrieSyncProgress(s.d.stateDB, s.d.syncStatsState.processed) + rawdb.WriteFastTrieProgress(s.d.stateDB, s.d.syncStatsState.processed) } } diff --git a/eth/fetcher/fetcher_test.go b/eth/fetcher/fetcher_test.go index 9d53b98b60..3d4f0d1e59 100644 --- a/eth/fetcher/fetcher_test.go +++ b/eth/fetcher/fetcher_test.go @@ -34,7 +34,7 @@ import ( ) var ( - testdb, _ = ethdb.NewMemDatabase() + testdb = ethdb.NewMemDatabase() testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testAddress = crypto.PubkeyToAddress(testKey.PublicKey) genesis = core.GenesisBlockForTesting(testdb, testAddress, big.NewInt(1000000000)) @@ -198,7 +198,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ } } -// verifyFetchingEvent verifies that one single event arrive on an fetching channel. +// verifyFetchingEvent verifies that one single event arrive on a fetching channel. func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) { if arrive { select { diff --git a/eth/filters/api.go b/eth/filters/api.go index ec403709c5..1297b74788 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -268,14 +268,8 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc } // FilterCriteria represents a request to create a new filter. -// -// TODO(karalabe): Kill this in favor of ethereum.FilterQuery. -type FilterCriteria struct { - FromBlock *big.Int - ToBlock *big.Int - Addresses []common.Address - Topics [][]common.Hash -} +// Same as ethereum.FilterQuery but with UnmarshalJSON() method. +type FilterCriteria ethereum.FilterQuery // NewFilter creates a new filter and returns the filter id. It can be // used to retrieve logs when the state changes. This method cannot be diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 4ae37f9779..02229a7549 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -29,8 +29,8 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { var ( fromBlock rpc.BlockNumber = 0x123435 toBlock rpc.BlockNumber = 0xabcdef - address0 = common.StringToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d") - address1 = common.StringToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83") + address0 = common.HexToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d") + address1 = common.HexToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83") topic0 = common.HexToHash("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1ca") topic1 = common.HexToHash("9084a792d2f8b16a62b882fd56f7860c07bf5fa91dd8a2ae7e809e5180fef0b3") topic2 = common.HexToHash("6ccae1c4af4152f460ff510e573399795dfab5dcf1fa60d1f33ac8fdc1e480ce") diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index 0a0929bc10..faffaa70b7 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -25,8 +25,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -71,20 +71,20 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { if err != nil { b.Fatalf("error opening database at %v: %v", benchDataDir, err) } - head := core.GetHeadBlockHash(db) + head := rawdb.ReadHeadBlockHash(db) if head == (common.Hash{}) { b.Fatalf("chain data not found at %v", benchDataDir) } clearBloomBits(db) fmt.Println("Generating bloombits data...") - headNum := core.GetBlockNumber(db, head) - if headNum < sectionSize+512 { + headNum := rawdb.ReadHeaderNumber(db, head) + if headNum == nil || *headNum < sectionSize+512 { b.Fatalf("not enough blocks for running a benchmark") } start := time.Now() - cnt := (headNum - 512) / sectionSize + cnt := (*headNum - 512) / sectionSize var dataSize, compSize uint64 for sectionIdx := uint64(0); sectionIdx < cnt; sectionIdx++ { bc, err := bloombits.NewGenerator(uint(sectionSize)) @@ -93,14 +93,14 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { } var header *types.Header for i := sectionIdx * sectionSize; i < (sectionIdx+1)*sectionSize; i++ { - hash := core.GetCanonicalHash(db, i) - header = core.GetHeader(db, hash, i) + hash := rawdb.ReadCanonicalHash(db, i) + header = rawdb.ReadHeader(db, hash, i) if header == nil { b.Fatalf("Error creating bloomBits data") } bc.AddBloom(uint(i-sectionIdx*sectionSize), header.Bloom) } - sectionHead := core.GetCanonicalHash(db, (sectionIdx+1)*sectionSize-1) + sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*sectionSize-1) for i := 0; i < types.BloomBitLength; i++ { data, err := bc.Bitset(uint(i)) if err != nil { @@ -109,7 +109,7 @@ func benchmarkBloomBits(b *testing.B, sectionSize uint64) { comp := bitutil.CompressBytes(data) dataSize += uint64(len(data)) compSize += uint64(len(comp)) - core.WriteBloomBits(db, uint(i), sectionIdx, sectionHead, comp) + rawdb.WriteBloomBits(db, uint(i), sectionIdx, sectionHead, comp) } //if sectionIdx%50 == 0 { // fmt.Println(" section", sectionIdx, "/", cnt) @@ -180,11 +180,11 @@ func BenchmarkNoBloomBits(b *testing.B) { if err != nil { b.Fatalf("error opening database at %v: %v", benchDataDir, err) } - head := core.GetHeadBlockHash(db) + head := rawdb.ReadHeadBlockHash(db) if head == (common.Hash{}) { b.Fatalf("chain data not found at %v", benchDataDir) } - headNum := core.GetBlockNumber(db, head) + headNum := rawdb.ReadHeaderNumber(db, head) clearBloomBits(db) @@ -192,10 +192,10 @@ func BenchmarkNoBloomBits(b *testing.B) { start := time.Now() mux := new(event.TypeMux) backend := &testBackend{mux, db, 0, new(event.Feed), new(event.Feed), new(event.Feed), new(event.Feed)} - filter := New(backend, 0, int64(headNum), []common.Address{{}}, nil) + filter := New(backend, 0, int64(*headNum), []common.Address{{}}, nil) filter.Logs(context.Background()) d := time.Since(start) fmt.Println("Finished running filter benchmarks") - fmt.Println(" ", d, "total ", d*time.Duration(1000000)/time.Duration(headNum+1), "per million blocks") + fmt.Println(" ", d, "total ", d*time.Duration(1000000)/time.Duration(*headNum+1), "per million blocks") db.Close() } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index f8097c7b95..bb11734a76 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -28,8 +28,10 @@ import ( ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" ) @@ -91,8 +93,21 @@ type EventSystem struct { backend Backend lightMode bool lastHead *types.Header - install chan *subscription // install filter for event notification - uninstall chan *subscription // remove filter for event notification + + // Subscriptions + txSub event.Subscription // Subscription for new transaction event + logsSub event.Subscription // Subscription for new log event + rmLogsSub event.Subscription // Subscription for removed log event + chainSub event.Subscription // Subscription for new chain event + pendingLogSub *event.TypeMuxSubscription // Subscription for pending log event + + // Channels + install chan *subscription // install filter for event notification + uninstall chan *subscription // remove filter for event notification + txCh chan core.TxPreEvent // Channel to receive new transaction event + logsCh chan []*types.Log // Channel to receive new log event + rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event + chainCh chan core.ChainEvent // Channel to receive new chain event } // NewEventSystem creates a new manager that listens for event on the given mux, @@ -108,10 +123,27 @@ func NewEventSystem(mux *event.TypeMux, backend Backend, lightMode bool) *EventS lightMode: lightMode, install: make(chan *subscription), uninstall: make(chan *subscription), + txCh: make(chan core.TxPreEvent, txChanSize), + logsCh: make(chan []*types.Log, logsChanSize), + rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize), + chainCh: make(chan core.ChainEvent, chainEvChanSize), + } + + // Subscribe events + m.txSub = m.backend.SubscribeTxPreEvent(m.txCh) + m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh) + m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh) + m.chainSub = m.backend.SubscribeChainEvent(m.chainCh) + // TODO(rjl493456442): use feed to subscribe pending log event + m.pendingLogSub = m.mux.Subscribe(core.PendingLogsEvent{}) + + // Make sure none of the subscriptions are empty + if m.txSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || + m.pendingLogSub.Closed() { + log.Crit("Subscribe for event system failed") } go m.eventLoop() - return m } @@ -348,11 +380,11 @@ func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func for oldh.Hash() != newh.Hash() { if oldh.Number.Uint64() >= newh.Number.Uint64() { oldHeaders = append(oldHeaders, oldh) - oldh = core.GetHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1) + oldh = rawdb.ReadHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1) } if oldh.Number.Uint64() < newh.Number.Uint64() { newHeaders = append(newHeaders, newh) - newh = core.GetHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1) + newh = rawdb.ReadHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1) if newh == nil { // happens when CHT syncing, nothing to do newh = oldh @@ -411,52 +443,37 @@ func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common. // eventLoop (un)installs filters and processes mux events. func (es *EventSystem) eventLoop() { - var ( - index = make(filterIndex) - sub = es.mux.Subscribe(core.PendingLogsEvent{}) - // Subscribe TxPreEvent form txpool - txCh = make(chan core.TxPreEvent, txChanSize) - txSub = es.backend.SubscribeTxPreEvent(txCh) - // Subscribe RemovedLogsEvent - rmLogsCh = make(chan core.RemovedLogsEvent, rmLogsChanSize) - rmLogsSub = es.backend.SubscribeRemovedLogsEvent(rmLogsCh) - // Subscribe []*types.Log - logsCh = make(chan []*types.Log, logsChanSize) - logsSub = es.backend.SubscribeLogsEvent(logsCh) - // Subscribe ChainEvent - chainEvCh = make(chan core.ChainEvent, chainEvChanSize) - chainEvSub = es.backend.SubscribeChainEvent(chainEvCh) - ) - - // Unsubscribe all events - defer sub.Unsubscribe() - defer txSub.Unsubscribe() - defer rmLogsSub.Unsubscribe() - defer logsSub.Unsubscribe() - defer chainEvSub.Unsubscribe() + // Ensure all subscriptions get cleaned up + defer func() { + es.pendingLogSub.Unsubscribe() + es.txSub.Unsubscribe() + es.logsSub.Unsubscribe() + es.rmLogsSub.Unsubscribe() + es.chainSub.Unsubscribe() + }() + index := make(filterIndex) for i := UnknownSubscription; i < LastIndexSubscription; i++ { index[i] = make(map[rpc.ID]*subscription) } for { select { - case ev, active := <-sub.Chan(): + // Handle subscribed events + case ev := <-es.txCh: + es.broadcast(index, ev) + case ev := <-es.logsCh: + es.broadcast(index, ev) + case ev := <-es.rmLogsCh: + es.broadcast(index, ev) + case ev := <-es.chainCh: + es.broadcast(index, ev) + case ev, active := <-es.pendingLogSub.Chan(): if !active { // system stopped return } es.broadcast(index, ev) - // Handle subscribed events - case ev := <-txCh: - es.broadcast(index, ev) - case ev := <-rmLogsCh: - es.broadcast(index, ev) - case ev := <-logsCh: - es.broadcast(index, ev) - case ev := <-chainEvCh: - es.broadcast(index, ev) - case f := <-es.install: if f.typ == MinedAndPendingLogsSubscription { // the type are logs and pending logs subscriptions @@ -466,6 +483,7 @@ func (es *EventSystem) eventLoop() { index[f.typ][f.id] = f } close(f.installed) + case f := <-es.uninstall: if f.typ == MinedAndPendingLogsSubscription { // the type are logs and pending logs subscriptions @@ -477,13 +495,13 @@ func (es *EventSystem) eventLoop() { close(f.err) // System stopped - case <-txSub.Err(): + case <-es.txSub.Err(): return - case <-rmLogsSub.Err(): + case <-es.logsSub.Err(): return - case <-logsSub.Err(): + case <-es.rmLogsSub.Err(): return - case <-chainEvSub.Err(): + case <-es.chainSub.Err(): return } } diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 61761151a2..b4df24b471 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -56,26 +57,37 @@ func (b *testBackend) EventMux() *event.TypeMux { } func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) { - var hash common.Hash - var num uint64 + var ( + hash common.Hash + num uint64 + ) if blockNr == rpc.LatestBlockNumber { - hash = core.GetHeadBlockHash(b.db) - num = core.GetBlockNumber(b.db, hash) + hash = rawdb.ReadHeadBlockHash(b.db) + number := rawdb.ReadHeaderNumber(b.db, hash) + if number == nil { + return nil, nil + } + num = *number } else { num = uint64(blockNr) - hash = core.GetCanonicalHash(b.db, num) + hash = rawdb.ReadCanonicalHash(b.db, num) } - return core.GetHeader(b.db, hash, num), nil + return rawdb.ReadHeader(b.db, hash, num), nil } -func (b *testBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { - number := core.GetBlockNumber(b.db, blockHash) - return core.GetBlockReceipts(b.db, blockHash, number), nil +func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil { + return rawdb.ReadReceipts(b.db, hash, *number), nil + } + return nil, nil } -func (b *testBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) { - number := core.GetBlockNumber(b.db, blockHash) - receipts := core.GetBlockReceipts(b.db, blockHash, number) +func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { + number := rawdb.ReadHeaderNumber(b.db, hash) + if number == nil { + return nil, nil + } + receipts := rawdb.ReadReceipts(b.db, hash, *number) logs := make([][]*types.Log, len(receipts)) for i, receipt := range receipts { @@ -121,8 +133,8 @@ func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.Matc task.Bitsets = make([][]byte, len(task.Sections)) for i, section := range task.Sections { if rand.Int()%4 != 0 { // Handle occasional missing deliveries - head := core.GetCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1) - task.Bitsets[i], _ = core.GetBloomBits(b.db, task.Bit, section, head) + head := rawdb.ReadCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1) + task.Bitsets[i], _ = rawdb.ReadBloomBits(b.db, task.Bit, section, head) } } request <- task @@ -141,7 +153,7 @@ func TestBlockSubscription(t *testing.T) { var ( mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() txFeed = new(event.Feed) rmLogsFeed = new(event.Feed) logsFeed = new(event.Feed) @@ -198,7 +210,7 @@ func TestPendingTxFilter(t *testing.T) { var ( mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() txFeed = new(event.Feed) rmLogsFeed = new(event.Feed) logsFeed = new(event.Feed) @@ -261,7 +273,7 @@ func TestPendingTxFilter(t *testing.T) { func TestLogFilterCreation(t *testing.T) { var ( mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() txFeed = new(event.Feed) rmLogsFeed = new(event.Feed) logsFeed = new(event.Feed) @@ -310,7 +322,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { var ( mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() txFeed = new(event.Feed) rmLogsFeed = new(event.Feed) logsFeed = new(event.Feed) @@ -340,7 +352,7 @@ func TestLogFilter(t *testing.T) { var ( mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() txFeed = new(event.Feed) rmLogsFeed = new(event.Feed) logsFeed = new(event.Feed) @@ -459,7 +471,7 @@ func TestPendingLogsSubscription(t *testing.T) { var ( mux = new(event.TypeMux) - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() txFeed = new(event.Feed) rmLogsFeed = new(event.Feed) logsFeed = new(event.Feed) diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 0018142c46..ccabe955cf 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -84,16 +85,10 @@ func BenchmarkFilters(b *testing.B) { } }) for i, block := range chain { - core.WriteBlock(db, block) - if err := core.WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil { - b.Fatalf("failed to insert block number: %v", err) - } - if err := core.WriteHeadBlockHash(db, block.Hash()); err != nil { - b.Fatalf("failed to insert block number: %v", err) - } - if err := core.WriteBlockReceipts(db, block.Hash(), block.NumberU64(), receipts[i]); err != nil { - b.Fatal("error writing block receipts:", err) - } + rawdb.WriteBlock(db, block) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) } b.ResetTimer() @@ -174,16 +169,10 @@ func TestFilters(t *testing.T) { } }) for i, block := range chain { - core.WriteBlock(db, block) - if err := core.WriteCanonicalHash(db, block.Hash(), block.NumberU64()); err != nil { - t.Fatalf("failed to insert block number: %v", err) - } - if err := core.WriteHeadBlockHash(db, block.Hash()); err != nil { - t.Fatalf("failed to insert block number: %v", err) - } - if err := core.WriteBlockReceipts(db, block.Hash(), block.NumberU64(), receipts[i]); err != nil { - t.Fatal("error writing block receipts:", err) - } + rawdb.WriteBlock(db, block) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) } filter := New(backend, 0, -1, []common.Address{addr}, [][]common.Hash{{hash1, hash2, hash3, hash4}}) diff --git a/eth/handler.go b/eth/handler.go index 4069359c9e..c8f7e13f16 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -725,25 +725,25 @@ func (pm *ProtocolManager) BroadcastTx(hash common.Hash, tx *types.Transaction) } // Mined broadcast loop -func (self *ProtocolManager) minedBroadcastLoop() { +func (pm *ProtocolManager) minedBroadcastLoop() { // automatically stops if unsubscribe - for obj := range self.minedBlockSub.Chan() { + for obj := range pm.minedBlockSub.Chan() { switch ev := obj.Data.(type) { case core.NewMinedBlockEvent: - self.BroadcastBlock(ev.Block, true) // First propagate block to peers - self.BroadcastBlock(ev.Block, false) // Only then announce to the rest + pm.BroadcastBlock(ev.Block, true) // First propagate block to peers + pm.BroadcastBlock(ev.Block, false) // Only then announce to the rest } } } -func (self *ProtocolManager) txBroadcastLoop() { +func (pm *ProtocolManager) txBroadcastLoop() { for { select { - case event := <-self.txCh: - self.BroadcastTx(event.Tx.Hash(), event.Tx) + case event := <-pm.txCh: + pm.BroadcastTx(event.Tx.Hash(), event.Tx) // Err() channel will be closed when unsubscribing. - case <-self.txSub.Err(): + case <-pm.txSub.Err(): return } } @@ -760,13 +760,13 @@ type NodeInfo struct { } // NodeInfo retrieves some protocol metadata about the running host node. -func (self *ProtocolManager) NodeInfo() *NodeInfo { - currentBlock := self.blockchain.CurrentBlock() +func (pm *ProtocolManager) NodeInfo() *NodeInfo { + currentBlock := pm.blockchain.CurrentBlock() return &NodeInfo{ - Network: self.networkId, - Difficulty: self.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), - Genesis: self.blockchain.Genesis().Hash(), - Config: self.blockchain.Config(), + Network: pm.networkId, + Difficulty: pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), + Genesis: pm.blockchain.Genesis().Hash(), + Config: pm.blockchain.Config(), Head: currentBlock.Hash(), } } diff --git a/eth/handler_test.go b/eth/handler_test.go index e336dfa285..fee4114eb6 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -366,7 +366,7 @@ func testGetNodeData(t *testing.T, protocol int) { t.Errorf("data hash mismatch: have %x, want %x", hash, want) } } - statedb, _ := ethdb.NewMemDatabase() + statedb := ethdb.NewMemDatabase() for i := 0; i < len(data); i++ { statedb.Put(hashes[i].Bytes(), data[i]) } @@ -468,7 +468,7 @@ func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool var ( evmux = new(event.TypeMux) pow = ethash.NewFaker() - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() config = ¶ms.ChainConfig{DAOForkBlock: big.NewInt(1), DAOForkSupport: localForked} gspec = &core.Genesis{Config: config} genesis = gspec.MustCommit(db) diff --git a/eth/helper_test.go b/eth/helper_test.go index 2b05cea801..8a0260fc95 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -53,7 +53,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func var ( evmux = new(event.TypeMux) engine = ethash.NewFaker() - db, _ = ethdb.NewMemDatabase() + db = ethdb.NewMemDatabase() gspec = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{testBank: {Balance: big.NewInt(1000000)}}, diff --git a/eth/protocol.go b/eth/protocol.go index cd7db57f23..328d5b9939 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -34,13 +34,13 @@ const ( eth63 = 63 ) -// Official short name of the protocol used during capability negotiation. +// ProtocolName is the official short name of the protocol used during capability negotiation. var ProtocolName = "eth" -// Supported versions of the eth protocol (first is primary). +// ProtocolVersions are the upported versions of the eth protocol (first is primary). var ProtocolVersions = []uint{eth63, eth62} -// Number of implemented message corresponding to different protocol versions. +// ProtocolLengths are the number of implemented message corresponding to different protocol versions. var ProtocolLengths = []uint64{17, 8} const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index bf8120228f..d25fc459a1 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -159,8 +159,7 @@ func TestCallTracer(t *testing.T) { GasLimit: uint64(test.Context.GasLimit), GasPrice: tx.GasPrice(), } - db, _ := ethdb.NewMemDatabase() - statedb := tests.MakePreState(db, test.Genesis.Alloc) + statedb := tests.MakePreState(ethdb.NewMemDatabase(), test.Genesis.Alloc) // Create the tracer, the EVM environment and run it tracer, err := New("callTracer") diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 87a912901a..b482245878 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -39,7 +39,11 @@ type Client struct { // Dial connects a client to the given URL. func Dial(rawurl string) (*Client, error) { - c, err := rpc.Dial(rawurl) + return DialContext(context.Background(), rawurl) +} + +func DialContext(ctx context.Context, rawurl string) (*Client, error) { + c, err := rpc.DialContext(ctx, rawurl) if err != nil { return nil, err } @@ -51,6 +55,10 @@ func NewClient(c *rpc.Client) *Client { return &Client{c} } +func (ec *Client) Close() { + ec.c.Close() +} + // Blockchain Access // BlockByHash returns the given full block. @@ -296,7 +304,7 @@ func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, err // SubscribeNewHead subscribes to notifications about the current blockchain head // on the given channel. func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { - return ec.c.EthSubscribe(ctx, ch, "newHeads", map[string]struct{}{}) + return ec.c.EthSubscribe(ctx, ch, "newHeads") } // State Access diff --git a/ethdb/database.go b/ethdb/database.go index 30ed37dc7b..001d8f0bb9 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -17,6 +17,7 @@ package ethdb import ( + "fmt" "strconv" "strings" "sync" @@ -32,17 +33,25 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) +const ( + writeDelayNThreshold = 200 + writeDelayThreshold = 350 * time.Millisecond + writeDelayWarningThrottler = 1 * time.Minute +) + var OpenFileLimit = 64 type LDBDatabase struct { fn string // filename for reporting db *leveldb.DB // LevelDB instance - compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction - compReadMeter metrics.Meter // Meter for measuring the data read during compaction - compWriteMeter metrics.Meter // Meter for measuring the data written during compaction - diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read - diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written + compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction + compReadMeter metrics.Meter // Meter for measuring the data read during compaction + compWriteMeter metrics.Meter // Meter for measuring the data written during compaction + writeDelayNMeter metrics.Meter // Meter for measuring the write delay number due to database compaction + writeDelayMeter metrics.Meter // Meter for measuring the write delay duration due to database compaction + diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read + diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written quitLock sync.Mutex // Mutex protecting the quit channel access quitChan chan chan error // Quit channel to stop the metrics collection before closing the database @@ -91,9 +100,6 @@ func (db *LDBDatabase) Path() string { // Put puts the given key / value to the queue func (db *LDBDatabase) Put(key []byte, value []byte) error { - // Generate the data to write to disk, update the meter and write - //value = rle.Compress(value) - return db.db.Put(key, value, nil) } @@ -103,18 +109,15 @@ func (db *LDBDatabase) Has(key []byte) (bool, error) { // Get returns the given key if it's present. func (db *LDBDatabase) Get(key []byte) ([]byte, error) { - // Retrieve the key and increment the miss counter if not found dat, err := db.db.Get(key, nil) if err != nil { return nil, err } return dat, nil - //return rle.Decompress(dat) } // Delete deletes the key from the queue and database func (db *LDBDatabase) Delete(key []byte) error { - // Execute the actual operation return db.db.Delete(key, nil) } @@ -153,16 +156,17 @@ func (db *LDBDatabase) LDB() *leveldb.DB { // Meter configures the database metrics collectors and func (db *LDBDatabase) Meter(prefix string) { - // Short circuit metering if the metrics system is disabled - if !metrics.Enabled { - return + if metrics.Enabled { + // Initialize all the metrics collector at the requested prefix + db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil) + db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil) + db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil) + db.diskReadMeter = metrics.NewRegisteredMeter(prefix+"disk/read", nil) + db.diskWriteMeter = metrics.NewRegisteredMeter(prefix+"disk/write", nil) } - // Initialize all the metrics collector at the requested prefix - db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil) - db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil) - db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil) - db.diskReadMeter = metrics.NewRegisteredMeter(prefix+"disk/read", nil) - db.diskWriteMeter = metrics.NewRegisteredMeter(prefix+"disk/write", nil) + // Initialize write delay metrics no matter we are in metric mode or not. + db.writeDelayMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/duration", nil) + db.writeDelayNMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/counter", nil) // Create a quit channel for the periodic collector and run it db.quitLock.Lock() @@ -184,6 +188,9 @@ func (db *LDBDatabase) Meter(prefix string) { // 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884 // 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000 // +// This is how the write delay look like (currently): +// DelayN:5 Delay:406.604657ms +// // This is how the iostats look like (currently): // Read(MB):3895.04860 Write(MB):3654.64712 func (db *LDBDatabase) meter(refresh time.Duration) { @@ -194,6 +201,14 @@ func (db *LDBDatabase) meter(refresh time.Duration) { } // Create storage for iostats. var iostats [2]float64 + + // Create storage and warning log tracer for write delay. + var ( + delaystats [2]int64 + lastWriteDelay time.Time + lastWriteDelayN time.Time + ) + // Iterate ad infinitum and collect the stats for i := 1; ; i++ { // Retrieve the database stats @@ -242,6 +257,52 @@ func (db *LDBDatabase) meter(refresh time.Duration) { db.compWriteMeter.Mark(int64((compactions[i%2][2] - compactions[(i-1)%2][2]) * 1024 * 1024)) } + // Retrieve the write delay statistic + writedelay, err := db.db.GetProperty("leveldb.writedelay") + if err != nil { + db.log.Error("Failed to read database write delay statistic", "err", err) + return + } + var ( + delayN int64 + delayDuration string + duration time.Duration + ) + if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s", &delayN, &delayDuration); n != 2 || err != nil { + db.log.Error("Write delay statistic not found") + return + } + duration, err = time.ParseDuration(delayDuration) + if err != nil { + db.log.Error("Failed to parse delay duration", "err", err) + return + } + if db.writeDelayNMeter != nil { + db.writeDelayNMeter.Mark(delayN - delaystats[0]) + // If the write delay number been collected in the last minute exceeds the predefined threshold, + // print a warning log here. + // If a warning that db performance is laggy has been displayed, + // any subsequent warnings will be withhold for 1 minute to don't overwhelm the user. + if int(db.writeDelayNMeter.Rate1()) > writeDelayNThreshold && + time.Now().After(lastWriteDelayN.Add(writeDelayWarningThrottler)) { + db.log.Warn("Write delay number exceeds the threshold (200 per second) in the last minute") + lastWriteDelayN = time.Now() + } + } + if db.writeDelayMeter != nil { + db.writeDelayMeter.Mark(duration.Nanoseconds() - delaystats[1]) + // If the write delay duration been collected in the last minute exceeds the predefined threshold, + // print a warning log here. + // If a warning that db performance is laggy has been displayed, + // any subsequent warnings will be withhold for 1 minute to don't overwhelm the user. + if int64(db.writeDelayMeter.Rate1()) > writeDelayThreshold.Nanoseconds() && + time.Now().After(lastWriteDelay.Add(writeDelayWarningThrottler)) { + db.log.Warn("Write delay duration exceeds the threshold (35% of the time) in the last minute") + lastWriteDelay = time.Now() + } + } + delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() + // Retrieve the database iostats. ioStats, err := db.db.GetProperty("leveldb.iostats") if err != nil { diff --git a/ethdb/database_test.go b/ethdb/database_test.go index 5e4a3ca34a..2deb50988c 100644 --- a/ethdb/database_test.go +++ b/ethdb/database_test.go @@ -53,8 +53,7 @@ func TestLDB_PutGet(t *testing.T) { } func TestMemoryDB_PutGet(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - testPutGet(db, t) + testPutGet(ethdb.NewMemDatabase(), t) } func testPutGet(db ethdb.Database, t *testing.T) { @@ -131,8 +130,7 @@ func TestLDB_ParallelPutGet(t *testing.T) { } func TestMemoryDB_ParallelPutGet(t *testing.T) { - db, _ := ethdb.NewMemDatabase() - testParallelPutGet(db, t) + testParallelPutGet(ethdb.NewMemDatabase(), t) } func testParallelPutGet(db ethdb.Database, t *testing.T) { diff --git a/ethdb/memory_database.go b/ethdb/memory_database.go index 8efd7bf845..c57042920a 100644 --- a/ethdb/memory_database.go +++ b/ethdb/memory_database.go @@ -31,16 +31,16 @@ type MemDatabase struct { lock sync.RWMutex } -func NewMemDatabase() (*MemDatabase, error) { +func NewMemDatabase() *MemDatabase { return &MemDatabase{ db: make(map[string][]byte), - }, nil + } } -func NewMemDatabaseWithCap(size int) (*MemDatabase, error) { +func NewMemDatabaseWithCap(size int) *MemDatabase { return &MemDatabase{ db: make(map[string][]byte, size), - }, nil + } } func (db *MemDatabase) Put(key []byte, value []byte) error { diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index ae7e252654..a15d846150 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -689,7 +689,7 @@ func (s *Service) reportStats(conn *websocket.Conn) error { sync := s.eth.Downloader().Progress() syncing = s.eth.BlockChain().CurrentHeader().Number.Uint64() >= sync.HighestBlock - price, _ := s.eth.ApiBackend.SuggestPrice(context.Background()) + price, _ := s.eth.APIBackend.SuggestPrice(context.Background()) gasprice = int(price.Uint64()) } else { sync := s.les.Downloader().Progress() diff --git a/event/event.go b/event/event.go index 20d20d1f57..4232787314 100644 --- a/event/event.go +++ b/event/event.go @@ -180,6 +180,12 @@ func (s *TypeMuxSubscription) Unsubscribe() { s.closewait() } +func (s *TypeMuxSubscription) Closed() bool { + s.closeMu.Lock() + defer s.closeMu.Unlock() + return s.closed +} + func (s *TypeMuxSubscription) closewait() { s.closeMu.Lock() defer s.closeMu.Unlock() diff --git a/event/feed.go b/event/feed.go index 78fa3d98d8..f578f00c10 100644 --- a/event/feed.go +++ b/event/feed.go @@ -148,7 +148,9 @@ func (f *Feed) Send(value interface{}) (nsent int) { f.sendCases[i].Send = rvalue } - // Send until all channels except removeSub have been chosen. + // Send until all channels except removeSub have been chosen. 'cases' tracks a prefix + // of sendCases. When a send succeeds, the corresponding case moves to the end of + // 'cases' and it shrinks by one element. cases := f.sendCases for { // Fast path: try sending without blocking before adding to the select set. @@ -170,6 +172,7 @@ func (f *Feed) Send(value interface{}) (nsent int) { index := f.sendCases.find(recv.Interface()) f.sendCases = f.sendCases.delete(index) if index >= 0 && index < len(cases) { + // Shrink 'cases' too because the removed case was still active. cases = f.sendCases[:len(cases)-1] } } else { diff --git a/event/feed_test.go b/event/feed_test.go index a82c103033..be8876932c 100644 --- a/event/feed_test.go +++ b/event/feed_test.go @@ -235,6 +235,45 @@ func TestFeedUnsubscribeBlockedPost(t *testing.T) { wg.Wait() } +// Checks that unsubscribing a channel during Send works even if that +// channel has already been sent on. +func TestFeedUnsubscribeSentChan(t *testing.T) { + var ( + feed Feed + ch1 = make(chan int) + ch2 = make(chan int) + sub1 = feed.Subscribe(ch1) + sub2 = feed.Subscribe(ch2) + wg sync.WaitGroup + ) + defer sub2.Unsubscribe() + + wg.Add(1) + go func() { + feed.Send(0) + wg.Done() + }() + + // Wait for the value on ch1. + <-ch1 + // Unsubscribe ch1, removing it from the send cases. + sub1.Unsubscribe() + + // Receive ch2, finishing Send. + <-ch2 + wg.Wait() + + // Send again. This should send to ch2 only, so the wait group will unblock + // as soon as a value is received on ch2. + wg.Add(1) + go func() { + feed.Send(0) + wg.Done() + }() + <-ch2 + wg.Wait() +} + func TestFeedUnsubscribeFromInbox(t *testing.T) { var ( feed Feed diff --git a/event/filter/filter.go b/event/filter/filter.go index b1fbf30ee4..a6fe46d6a0 100644 --- a/event/filter/filter.go +++ b/event/filter/filter.go @@ -45,37 +45,37 @@ func New() *Filters { } } -func (self *Filters) Start() { - go self.loop() +func (f *Filters) Start() { + go f.loop() } -func (self *Filters) Stop() { - close(self.quit) +func (f *Filters) Stop() { + close(f.quit) } -func (self *Filters) Notify(filter Filter, data interface{}) { - self.ch <- FilterEvent{filter, data} +func (f *Filters) Notify(filter Filter, data interface{}) { + f.ch <- FilterEvent{filter, data} } -func (self *Filters) Install(watcher Filter) int { - self.watchers[self.id] = watcher - self.id++ +func (f *Filters) Install(watcher Filter) int { + f.watchers[f.id] = watcher + f.id++ - return self.id - 1 + return f.id - 1 } -func (self *Filters) Uninstall(id int) { - delete(self.watchers, id) +func (f *Filters) Uninstall(id int) { + delete(f.watchers, id) } -func (self *Filters) loop() { +func (f *Filters) loop() { out: for { select { - case <-self.quit: + case <-f.quit: break out - case event := <-self.ch: - for _, watcher := range self.watchers { + case event := <-f.ch: + for _, watcher := range f.watchers { if reflect.TypeOf(watcher) == reflect.TypeOf(event.filter) { if watcher.Compare(event.filter) { watcher.Trigger(event.data) @@ -86,10 +86,10 @@ out: } } -func (self *Filters) Match(a, b Filter) bool { +func (f *Filters) Match(a, b Filter) bool { return reflect.TypeOf(a) == reflect.TypeOf(b) && a.Compare(b) } -func (self *Filters) Get(i int) Filter { - return self.watchers[i] +func (f *Filters) Get(i int) Filter { + return f.watchers[i] } diff --git a/internal/build/pgp.go b/internal/build/pgp.go index 79ab9c06f1..c7d0d23397 100644 --- a/internal/build/pgp.go +++ b/internal/build/pgp.go @@ -57,3 +57,15 @@ func PGPSignFile(input string, output string, pgpkey string) error { // Generate the signature and return return openpgp.ArmoredDetachSign(out, keys[0], in, nil) } + +// PGPKeyID parses an armored key and returns the key ID. +func PGPKeyID(pgpkey string) (string, error) { + keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(pgpkey)) + if err != nil { + return "", err + } + if len(keys) != 1 { + return "", fmt.Errorf("key count mismatch: have %d, want %d", len(keys), 1) + } + return keys[0].PrimaryKey.KeyIdString(), nil +} diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 1f181bf8b0..5eb58e9eef 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -28,10 +28,13 @@ import ( "github.com/ethereum/go-ethereum/log/term" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" + "github.com/fjl/memsize/memsizeui" colorable "github.com/mattn/go-colorable" "gopkg.in/urfave/cli.v1" ) +var Memsize memsizeui.Handler + var ( verbosityFlag = cli.IntFlag{ Name: "verbosity", @@ -129,21 +132,25 @@ func Setup(ctx *cli.Context) error { // pprof server if ctx.GlobalBool(pprofFlag.Name) { - // Hook go-metrics into expvar on any /debug/metrics request, load all vars - // from the registry into expvar, and execute regular expvar handler. - exp.Exp(metrics.DefaultRegistry) - address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name)) - go func() { - log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) - if err := http.ListenAndServe(address, nil); err != nil { - log.Error("Failure in running pprof server", "err", err) - } - }() + StartPProf(address) } return nil } +func StartPProf(address string) { + // Hook go-metrics into expvar on any /debug/metrics request, load all vars + // from the registry into expvar, and execute regular expvar handler. + exp.Exp(metrics.DefaultRegistry) + http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) + log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) + go func() { + if err := http.ListenAndServe(address, nil); err != nil { + log.Error("Failure in running pprof server", "err", err) + } + }() +} + // Exit stops all running profiles, flushing their output to the // respective file. func Exit() { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e2bfbaf307..a524dbadd8 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -1006,7 +1007,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, addr // GetTransactionByHash returns the transaction for the given hash func (s *PublicTransactionPoolAPI) GetTransactionByHash(ctx context.Context, hash common.Hash) *RPCTransaction { // Try to return an already finalized transaction - if tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash); tx != nil { + if tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash); tx != nil { return newRPCTransaction(tx, blockHash, blockNumber, index) } // No finalized transaction, try to retrieve it from the pool @@ -1022,7 +1023,7 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, var tx *types.Transaction // Retrieve a finalized transaction, or a pooled otherwise - if tx, _, _, _ = core.GetTransaction(s.b.ChainDb(), hash); tx == nil { + if tx, _, _, _ = rawdb.ReadTransaction(s.b.ChainDb(), hash); tx == nil { if tx = s.b.GetPoolTransaction(hash); tx == nil { // Transaction not found anywhere, abort return nil, nil @@ -1034,7 +1035,7 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByHash(ctx context.Context, // GetTransactionReceipt returns the transaction receipt for the given transaction hash. func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { - tx, blockHash, blockNumber, index := core.GetTransaction(s.b.ChainDb(), hash) + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(s.b.ChainDb(), hash) if tx == nil { return nil, nil } diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js index 9bb899384b..abd4b4fe58 100644 --- a/internal/jsre/deps/web3.js +++ b/internal/jsre/deps/web3.js @@ -2193,7 +2193,7 @@ var toWei = function(number, unit) { }; /** - * Takes an input and transforms it into an bignumber + * Takes an input and transforms it into a bignumber * * @method toBigNumber * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber diff --git a/internal/jsre/jsre.go b/internal/jsre/jsre.go index f05865eca6..4c7664f1cc 100644 --- a/internal/jsre/jsre.go +++ b/internal/jsre/jsre.go @@ -102,8 +102,8 @@ func randomSource() *rand.Rand { // call the functions of the otto vm directly to circumvent the queue. These // functions should be used if and only if running a routine that was already // called from JS through an RPC call. -func (self *JSRE) runEventLoop() { - defer close(self.closed) +func (re *JSRE) runEventLoop() { + defer close(re.closed) vm := otto.New() r := randomSource() @@ -202,14 +202,14 @@ loop: break loop } } - case req := <-self.evalQueue: + case req := <-re.evalQueue: // run the code, send the result back req.fn(vm) close(req.done) if waitForCallbacks && (len(registry) == 0) { break loop } - case waitForCallbacks = <-self.stopEventLoop: + case waitForCallbacks = <-re.stopEventLoop: if !waitForCallbacks || (len(registry) == 0) { break loop } @@ -223,31 +223,31 @@ loop: } // Do executes the given function on the JS event loop. -func (self *JSRE) Do(fn func(*otto.Otto)) { +func (re *JSRE) Do(fn func(*otto.Otto)) { done := make(chan bool) req := &evalReq{fn, done} - self.evalQueue <- req + re.evalQueue <- req <-done } // stops the event loop before exit, optionally waits for all timers to expire -func (self *JSRE) Stop(waitForCallbacks bool) { +func (re *JSRE) Stop(waitForCallbacks bool) { select { - case <-self.closed: - case self.stopEventLoop <- waitForCallbacks: - <-self.closed + case <-re.closed: + case re.stopEventLoop <- waitForCallbacks: + <-re.closed } } // Exec(file) loads and runs the contents of a file // if a relative path is given, the jsre's assetPath is used -func (self *JSRE) Exec(file string) error { - code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file)) +func (re *JSRE) Exec(file string) error { + code, err := ioutil.ReadFile(common.AbsolutePath(re.assetPath, file)) if err != nil { return err } var script *otto.Script - self.Do(func(vm *otto.Otto) { + re.Do(func(vm *otto.Otto) { script, err = vm.Compile(file, code) if err != nil { return @@ -259,36 +259,36 @@ func (self *JSRE) Exec(file string) error { // Bind assigns value v to a variable in the JS environment // This method is deprecated, use Set. -func (self *JSRE) Bind(name string, v interface{}) error { - return self.Set(name, v) +func (re *JSRE) Bind(name string, v interface{}) error { + return re.Set(name, v) } // Run runs a piece of JS code. -func (self *JSRE) Run(code string) (v otto.Value, err error) { - self.Do(func(vm *otto.Otto) { v, err = vm.Run(code) }) +func (re *JSRE) Run(code string) (v otto.Value, err error) { + re.Do(func(vm *otto.Otto) { v, err = vm.Run(code) }) return v, err } // Get returns the value of a variable in the JS environment. -func (self *JSRE) Get(ns string) (v otto.Value, err error) { - self.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) }) +func (re *JSRE) Get(ns string) (v otto.Value, err error) { + re.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) }) return v, err } // Set assigns value v to a variable in the JS environment. -func (self *JSRE) Set(ns string, v interface{}) (err error) { - self.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) }) +func (re *JSRE) Set(ns string, v interface{}) (err error) { + re.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) }) return err } // loadScript executes a JS script from inside the currently executing JS code. -func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value { +func (re *JSRE) loadScript(call otto.FunctionCall) otto.Value { file, err := call.Argument(0).ToString() if err != nil { // TODO: throw exception return otto.FalseValue() } - file = common.AbsolutePath(self.assetPath, file) + file = common.AbsolutePath(re.assetPath, file) source, err := ioutil.ReadFile(file) if err != nil { // TODO: throw exception @@ -305,10 +305,10 @@ func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value { // Evaluate executes code and pretty prints the result to the specified output // stream. -func (self *JSRE) Evaluate(code string, w io.Writer) error { +func (re *JSRE) Evaluate(code string, w io.Writer) error { var fail error - self.Do(func(vm *otto.Otto) { + re.Do(func(vm *otto.Otto) { val, err := vm.Run(code) if err != nil { prettyError(vm, err, w) @@ -321,8 +321,8 @@ func (self *JSRE) Evaluate(code string, w io.Writer) error { } // Compile compiles and then runs a piece of JS code. -func (self *JSRE) Compile(filename string, src interface{}) (err error) { - self.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) }) +func (re *JSRE) Compile(filename string, src interface{}) (err error) { + re.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) }) return err } diff --git a/les/api_backend.go b/les/api_backend.go index 3fc5c33a44..1d3c995134 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -83,16 +84,22 @@ func (b *LesApiBackend) GetBlock(ctx context.Context, blockHash common.Hash) (*t return b.eth.blockchain.GetBlockByHash(ctx, blockHash) } -func (b *LesApiBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { - return light.GetBlockReceipts(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)) +func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { + if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { + return light.GetBlockReceipts(ctx, b.eth.odr, hash, *number) + } + return nil, nil } -func (b *LesApiBackend) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) { - return light.GetBlockLogs(ctx, b.eth.odr, blockHash, core.GetBlockNumber(b.eth.chainDb, blockHash)) +func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) { + if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { + return light.GetBlockLogs(ctx, b.eth.odr, hash, *number) + } + return nil, nil } -func (b *LesApiBackend) GetTd(blockHash common.Hash) *big.Int { - return b.eth.blockchain.GetTdByHash(blockHash) +func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { + return b.eth.blockchain.GetTdByHash(hash) } func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmCfg vm.Config) (*vm.EVM, func() error, error) { diff --git a/les/backend.go b/les/backend.go index 6a324cb04b..2d8ada7b0e 100644 --- a/les/backend.go +++ b/les/backend.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" @@ -122,7 +123,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) leth.blockchain.SetHead(compat.RewindTo) - core.WriteChainConfig(chainDb, genesisHash, chainConfig) + rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) diff --git a/les/bloombits.go b/les/bloombits.go index de233d7518..2871a90064 100644 --- a/les/bloombits.go +++ b/les/bloombits.go @@ -72,13 +72,3 @@ func (eth *LightEthereum) startBloomHandlers() { }() } } - -const ( - // bloomConfirms is the number of confirmation blocks before a bloom section is - // considered probably final and its rotated bits are calculated. - bloomConfirms = 256 - - // bloomThrottling is the time to wait between processing two consecutive index - // sections. It's useful during chain upgrades to prevent disk overload. - bloomThrottling = 100 * time.Millisecond -) diff --git a/les/distributor_test.go b/les/distributor_test.go index 55defb69be..2891bcab49 100644 --- a/les/distributor_test.go +++ b/les/distributor_test.go @@ -97,9 +97,8 @@ func (p *testDistPeer) waitBefore(cost uint64) (time.Duration, float64) { p.lock.RUnlock() if sumCost < testDistBufLimit { return 0, float64(testDistBufLimit-sumCost) / float64(testDistBufLimit) - } else { - return time.Duration(sumCost - testDistBufLimit), 0 } + return time.Duration(sumCost - testDistBufLimit), 0 } func (p *testDistPeer) canQueue() bool { diff --git a/les/fetcher.go b/les/fetcher.go index e12a2c78a2..59d3a2aa3c 100644 --- a/les/fetcher.go +++ b/les/fetcher.go @@ -25,7 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" @@ -280,7 +280,7 @@ func (f *lightFetcher) announce(p *peer, head *announceData) { // if one of root's children is canonical, keep it, delete other branches and root itself var newRoot *fetcherTreeNode for i, nn := range fp.root.children { - if core.GetCanonicalHash(f.pm.chainDb, nn.number) == nn.hash { + if rawdb.ReadCanonicalHash(f.pm.chainDb, nn.number) == nn.hash { fp.root.children = append(fp.root.children[:i], fp.root.children[i+1:]...) nn.parent = nil newRoot = nn @@ -363,7 +363,7 @@ func (f *lightFetcher) peerHasBlock(p *peer, hash common.Hash, number uint64) bo // // when syncing, just check if it is part of the known chain, there is nothing better we // can do since we do not know the most recent block hash yet - return core.GetCanonicalHash(f.pm.chainDb, fp.root.number) == fp.root.hash && core.GetCanonicalHash(f.pm.chainDb, number) == hash + return rawdb.ReadCanonicalHash(f.pm.chainDb, fp.root.number) == fp.root.hash && rawdb.ReadCanonicalHash(f.pm.chainDb, number) == hash } // requestAmount calculates the amount of headers to be downloaded starting diff --git a/les/handler.go b/les/handler.go index 9627f392be..22899eb1bc 100644 --- a/les/handler.go +++ b/les/handler.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" @@ -528,9 +529,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { break } // Retrieve the requested block body, stopping if enough was found - if data := core.GetBodyRLP(pm.chainDb, hash, core.GetBlockNumber(pm.chainDb, hash)); len(data) != 0 { - bodies = append(bodies, data) - bytes += len(data) + if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil { + if data := rawdb.ReadBodyRLP(pm.chainDb, hash, *number); len(data) != 0 { + bodies = append(bodies, data) + bytes += len(data) + } } } bv, rcost := p.fcClient.RequestProcessed(costs.baseCost + uint64(reqCnt)*costs.reqCost) @@ -579,20 +582,22 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } for _, req := range req.Reqs { // Retrieve the requested state entry, stopping if enough was found - if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil { - statedb, err := pm.blockchain.State() - if err != nil { - continue - } - account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) - if err != nil { - continue - } - code, _ := statedb.Database().TrieDB().Node(common.BytesToHash(account.CodeHash)) + if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { + if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { + statedb, err := pm.blockchain.State() + if err != nil { + continue + } + account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) + if err != nil { + continue + } + code, _ := statedb.Database().TrieDB().Node(common.BytesToHash(account.CodeHash)) - data = append(data, code) - if bytes += len(code); bytes >= softResponseLimit { - break + data = append(data, code) + if bytes += len(code); bytes >= softResponseLimit { + break + } } } } @@ -645,7 +650,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { break } // Retrieve the requested block's receipts, skipping if unknown to us - results := core.GetBlockReceipts(pm.chainDb, hash, core.GetBlockNumber(pm.chainDb, hash)) + var results types.Receipts + if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil { + results = rawdb.ReadReceipts(pm.chainDb, hash, *number) + } if results == nil { if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { continue @@ -705,28 +713,30 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { } for _, req := range req.Reqs { // Retrieve the requested state entry, stopping if enough was found - if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil { - statedb, err := pm.blockchain.State() - if err != nil { - continue - } - var trie state.Trie - if len(req.AccKey) > 0 { - account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) + if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { + if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { + statedb, err := pm.blockchain.State() if err != nil { continue } - trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root) - } else { - trie, _ = statedb.Database().OpenTrie(header.Root) - } - if trie != nil { - var proof light.NodeList - trie.Prove(req.Key, 0, &proof) + var trie state.Trie + if len(req.AccKey) > 0 { + account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey)) + if err != nil { + continue + } + trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root) + } else { + trie, _ = statedb.Database().OpenTrie(header.Root) + } + if trie != nil { + var proof light.NodeList + trie.Prove(req.Key, 0, &proof) - proofs = append(proofs, proof) - if bytes += proof.DataSize(); bytes >= softResponseLimit { - break + proofs = append(proofs, proof) + if bytes += proof.DataSize(); bytes >= softResponseLimit { + break + } } } } @@ -763,9 +773,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { if statedb == nil || req.BHash != lastBHash { statedb, root, lastBHash = nil, common.Hash{}, req.BHash - if header := core.GetHeader(pm.chainDb, req.BHash, core.GetBlockNumber(pm.chainDb, req.BHash)); header != nil { - statedb, _ = pm.blockchain.State() - root = header.Root + if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil { + if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil { + statedb, _ = pm.blockchain.State() + root = header.Root + } } } if statedb == nil { @@ -859,7 +871,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { trieDb := trie.NewDatabase(ethdb.NewTable(pm.chainDb, light.ChtTablePrefix)) for _, req := range req.Reqs { if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil { - sectionHead := core.GetCanonicalHash(pm.chainDb, req.ChtNum*light.CHTFrequencyServer-1) + sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, req.ChtNum*light.CHTFrequencyServer-1) if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) { trie, err := trie.New(root, trieDb) if err != nil { @@ -1114,10 +1126,10 @@ func (pm *ProtocolManager) getAccount(statedb *state.StateDB, root, hash common. func (pm *ProtocolManager) getHelperTrie(id uint, idx uint64) (common.Hash, string) { switch id { case htCanonical: - sectionHead := core.GetCanonicalHash(pm.chainDb, (idx+1)*light.CHTFrequencyClient-1) + sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, (idx+1)*light.CHTFrequencyClient-1) return light.GetChtV2Root(pm.chainDb, idx, sectionHead), light.ChtTablePrefix case htBloomBits: - sectionHead := core.GetCanonicalHash(pm.chainDb, (idx+1)*light.BloomTrieFrequency-1) + sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, (idx+1)*light.BloomTrieFrequency-1) return light.GetBloomTrieRoot(pm.chainDb, idx, sectionHead), light.BloomTrieTablePrefix } return common.Hash{}, "" @@ -1128,8 +1140,8 @@ func (pm *ProtocolManager) getHelperTrieAuxData(req HelperTrieReq) []byte { switch { case req.Type == htCanonical && req.AuxReq == auxHeader && len(req.Key) == 8: blockNum := binary.BigEndian.Uint64(req.Key) - hash := core.GetCanonicalHash(pm.chainDb, blockNum) - return core.GetHeaderRLP(pm.chainDb, hash, blockNum) + hash := rawdb.ReadCanonicalHash(pm.chainDb, blockNum) + return rawdb.ReadHeaderRLP(pm.chainDb, hash, blockNum) } return nil } @@ -1142,9 +1154,9 @@ func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus { // If the transaction is unknown to the pool, try looking it up locally if stat == core.TxStatusUnknown { - if block, number, index := core.GetTxLookupEntry(pm.chainDb, hashes[i]); block != (common.Hash{}) { + if block, number, index := rawdb.ReadTxLookupEntry(pm.chainDb, hashes[i]); block != (common.Hash{}) { stats[i].Status = core.TxStatusIncluded - stats[i].Lookup = &core.TxLookupEntry{BlockHash: block, BlockIndex: number, Index: index} + stats[i].Lookup = &rawdb.TxLookupEntry{BlockHash: block, BlockIndex: number, Index: index} } } } diff --git a/les/handler_test.go b/les/handler_test.go index 9468032f67..31aad3ed45 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" @@ -50,8 +51,7 @@ func TestGetBlockHeadersLes1(t *testing.T) { testGetBlockHeaders(t, 1) } func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } func testGetBlockHeaders(t *testing.T, protocol int) { - db, _ := ethdb.NewMemDatabase() - pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil, nil, db) + pm := newTestProtocolManagerMust(t, false, downloader.MaxHashFetch+15, nil, nil, nil, ethdb.NewMemDatabase()) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", protocol, pm, true) defer peer.close() @@ -180,8 +180,7 @@ func TestGetBlockBodiesLes1(t *testing.T) { testGetBlockBodies(t, 1) } func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) } func testGetBlockBodies(t *testing.T, protocol int) { - db, _ := ethdb.NewMemDatabase() - pm := newTestProtocolManagerMust(t, false, downloader.MaxBlockFetch+15, nil, nil, nil, db) + pm := newTestProtocolManagerMust(t, false, downloader.MaxBlockFetch+15, nil, nil, nil, ethdb.NewMemDatabase()) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", protocol, pm, true) defer peer.close() @@ -258,8 +257,7 @@ func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) } func testGetCode(t *testing.T, protocol int) { // Assemble the test environment - db, _ := ethdb.NewMemDatabase() - pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db) + pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, ethdb.NewMemDatabase()) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", protocol, pm, true) defer peer.close() @@ -292,7 +290,7 @@ func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } func testGetReceipt(t *testing.T, protocol int) { // Assemble the test environment - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", protocol, pm, true) @@ -304,7 +302,7 @@ func testGetReceipt(t *testing.T, protocol int) { block := bc.GetBlockByNumber(i) hashes = append(hashes, block.Hash()) - receipts = append(receipts, core.GetBlockReceipts(db, block.Hash(), block.NumberU64())) + receipts = append(receipts, rawdb.ReadReceipts(db, block.Hash(), block.NumberU64())) } // Send the hash request and verify the response cost := peer.GetRequestCost(GetReceiptsMsg, len(hashes)) @@ -320,7 +318,7 @@ func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) } func testGetProofs(t *testing.T, protocol int) { // Assemble the test environment - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", protocol, pm, true) @@ -383,7 +381,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { frequency = uint64(light.CHTFrequencyServer) } // Assemble the test environment - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() pm := newTestProtocolManagerMust(t, false, int(frequency)+light.HelperTrieProcessConfirmations, testChainGen, nil, nil, db) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", protocol, pm, true) @@ -451,7 +449,7 @@ func testGetCHTProofs(t *testing.T, protocol int) { // Tests that bloombits proofs can be correctly retrieved. func TestGetBloombitsProofs(t *testing.T) { // Assemble the test environment - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() pm := newTestProtocolManagerMust(t, false, light.BloomTrieFrequency+256, testChainGen, nil, nil, db) bc := pm.blockchain.(*core.BlockChain) peer, _ := newTestPeer(t, "peer", 2, pm, true) @@ -490,7 +488,7 @@ func TestGetBloombitsProofs(t *testing.T) { } func TestTransactionStatusLes2(t *testing.T) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() pm := newTestProtocolManagerMust(t, false, 0, nil, nil, nil, db) chain := pm.blockchain.(*core.BlockChain) config := core.DefaultTxPoolConfig @@ -555,9 +553,9 @@ func TestTransactionStatusLes2(t *testing.T) { } // check if their status is included now - block1hash := core.GetCanonicalHash(db, 1) - test(tx1, false, txStatus{Status: core.TxStatusIncluded, Lookup: &core.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}}) - test(tx2, false, txStatus{Status: core.TxStatusIncluded, Lookup: &core.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}}) + block1hash := rawdb.ReadCanonicalHash(db, 1) + test(tx1, false, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}}) + test(tx2, false, txStatus{Status: core.TxStatusIncluded, Lookup: &rawdb.TxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}}) // create a reorg that rolls them back gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), db, 2, func(i int, block *core.BlockGen) {}) diff --git a/les/odr_requests.go b/les/odr_requests.go index 34d759dd2a..c7f06df94d 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -24,7 +24,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -110,7 +110,7 @@ func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { body := bodies[0] // Retrieve our stored header and validate block content against it - header := core.GetHeader(db, r.Hash, r.Number) + header := rawdb.ReadHeader(db, r.Hash, r.Number) if header == nil { return errHeaderUnavailable } @@ -166,7 +166,7 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { receipt := receipts[0] // Retrieve our stored header and validate receipt content against it - header := core.GetHeader(db, r.Hash, r.Number) + header := rawdb.ReadHeader(db, r.Hash, r.Number) if header == nil { return errHeaderUnavailable } diff --git a/les/odr_test.go b/les/odr_test.go index 88e121cda6..983f7262b0 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -63,9 +64,13 @@ func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, odrGetReceipts) } func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { var receipts types.Receipts if bc != nil { - receipts = core.GetBlockReceipts(db, bhash, core.GetBlockNumber(db, bhash)) + if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { + receipts = rawdb.ReadReceipts(db, bhash, *number) + } } else { - receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, core.GetBlockNumber(db, bhash)) + if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { + receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, *number) + } } if receipts == nil { return nil @@ -160,8 +165,8 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) { peers := newPeerSet() dist := newRequestDistributor(peers, make(chan struct{})) rm := newRetrieveManager(peers, dist, nil) - db, _ := ethdb.NewMemDatabase() - ldb, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() + ldb := ethdb.NewMemDatabase() odr := NewLesOdr(ldb, light.NewChtIndexer(db, true), light.NewBloomTrieIndexer(db, true), eth.NewBloomIndexer(db, light.BloomTrieFrequency), rm) pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db) lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb) @@ -178,7 +183,7 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) { test := func(expFail uint64) { for i := uint64(0); i <= pm.blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := core.GetCanonicalHash(db, i) + bhash := rawdb.ReadCanonicalHash(db, i) b1 := fn(light.NoOdr, db, pm.chainConfig, pm.blockchain.(*core.BlockChain), nil, bhash) ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) diff --git a/les/peer.go b/les/peer.go index caf5680778..eb7452e276 100644 --- a/les/peer.go +++ b/les/peer.go @@ -545,9 +545,11 @@ func (ps *peerSet) notify(n peerSetNotify) { func (ps *peerSet) Register(p *peer) error { ps.lock.Lock() if ps.closed { + ps.lock.Unlock() return errClosed } if _, ok := ps.peers[p.id]; ok { + ps.lock.Unlock() return errAlreadyRegistered } ps.peers[p.id] = p diff --git a/les/protocol.go b/les/protocol.go index e1c4625bce..ee4c223988 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/ethereum/go-ethereum/rlp" @@ -160,9 +161,8 @@ func (a *announceData) checkSignature(pubKey *ecdsa.PublicKey) error { pbytes := elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y) if bytes.Equal(pbytes, recPubkey) { return nil - } else { - return errors.New("Wrong signature") } + return errors.New("Wrong signature") } type blockInfo struct { @@ -224,6 +224,6 @@ type proofsData [][]rlp.RawValue type txStatus struct { Status core.TxStatus - Lookup *core.TxLookupEntry `rlp:"nil"` + Lookup *rawdb.TxLookupEntry `rlp:"nil"` Error string } diff --git a/les/randselect.go b/les/randselect.go index 1a9d0695bd..1cc1d3d3e0 100644 --- a/les/randselect.go +++ b/les/randselect.go @@ -118,17 +118,16 @@ func (n *wrsNode) insert(item wrsItem, weight int64) int { if n.level == 0 { n.items[branch] = item return branch - } else { - var subNode *wrsNode - if n.items[branch] == nil { - subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} - n.items[branch] = subNode - } else { - subNode = n.items[branch].(*wrsNode) - } - subIdx := subNode.insert(item, weight) - return subNode.maxItems*branch + subIdx } + var subNode *wrsNode + if n.items[branch] == nil { + subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} + n.items[branch] = subNode + } else { + subNode = n.items[branch].(*wrsNode) + } + subIdx := subNode.insert(item, weight) + return subNode.maxItems*branch + subIdx } // setWeight updates the weight of a certain item (which should exist) and returns @@ -162,12 +161,10 @@ func (n *wrsNode) choose(val int64) (wrsItem, int64) { if val < w { if n.level == 0 { return n.items[i].(wrsItem), n.weights[i] - } else { - return n.items[i].(*wrsNode).choose(val) } - } else { - val -= w + return n.items[i].(*wrsNode).choose(val) } + val -= w } panic(nil) } diff --git a/les/request_test.go b/les/request_test.go index c13625de8e..ba2f603d8b 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -22,7 +22,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" @@ -58,15 +58,22 @@ func TestTrieEntryAccessLes1(t *testing.T) { testAccess(t, 1, tfTrieEntryAccess) func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) } func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - return &light.TrieRequest{Id: light.StateTrieID(core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash))), Key: testBankSecureTrieKey} + if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { + return &light.TrieRequest{Id: light.StateTrieID(rawdb.ReadHeader(db, bhash, *number)), Key: testBankSecureTrieKey} + } + return nil } func TestCodeAccessLes1(t *testing.T) { testAccess(t, 1, tfCodeAccess) } func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) } -func tfCodeAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - header := core.GetHeader(db, bhash, core.GetBlockNumber(db, bhash)) +func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest { + number := rawdb.ReadHeaderNumber(db, bhash) + if number != nil { + return nil + } + header := rawdb.ReadHeader(db, bhash, *number) if header.Number.Uint64() < testContractDeployed { return nil } @@ -80,8 +87,8 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) { peers := newPeerSet() dist := newRequestDistributor(peers, make(chan struct{})) rm := newRetrieveManager(peers, dist, nil) - db, _ := ethdb.NewMemDatabase() - ldb, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() + ldb := ethdb.NewMemDatabase() odr := NewLesOdr(ldb, light.NewChtIndexer(db, true), light.NewBloomTrieIndexer(db, true), eth.NewBloomIndexer(db, light.BloomTrieFrequency), rm) pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db) @@ -99,7 +106,7 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) { test := func(expFail uint64) { for i := uint64(0); i <= pm.blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := core.GetCanonicalHash(db, i) + bhash := rawdb.ReadCanonicalHash(db, i) if req := fn(ldb, bhash, i); req != nil { ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() diff --git a/les/server.go b/les/server.go index 28b87008a0..9eb8ee7f97 100644 --- a/les/server.go +++ b/les/server.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" @@ -329,11 +330,11 @@ func (pm *ProtocolManager) blockLoop() { header := ev.Block.Header() hash := header.Hash() number := header.Number.Uint64() - td := core.GetTd(pm.chainDb, hash, number) + td := rawdb.ReadTd(pm.chainDb, hash, number) if td != nil && td.Cmp(lastBroadcastTd) > 0 { var reorg uint64 if lastHead != nil { - reorg = lastHead.Number.Uint64() - core.FindCommonAncestor(pm.chainDb, header, lastHead).Number.Uint64() + reorg = lastHead.Number.Uint64() - rawdb.FindCommonAncestor(pm.chainDb, header, lastHead).Number.Uint64() } lastHead = header lastBroadcastTd = td diff --git a/les/serverpool.go b/les/serverpool.go index a84c29c3ac..a39f883554 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -73,7 +73,6 @@ const ( // and a short term value which is adjusted exponentially with a factor of // pstatRecentAdjust with each dial/connection and also returned exponentially // to the average with the time constant pstatReturnToMeanTC - pstatRecentAdjust = 0.1 pstatReturnToMeanTC = time.Hour // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after // each unsuccessful connection (restored after a successful one) @@ -83,9 +82,6 @@ const ( responseScoreTC = time.Millisecond * 100 delayScoreTC = time.Second * 5 timeoutPow = 10 - // peerSelectMinWeight is added to calculated weights at request peer selection - // to give poorly performing peers a little chance of coming back - peerSelectMinWeight = 0.005 // initStatsWeight is used to initialize previously unknown peers with good // statistics to give a chance to prove themselves initStatsWeight = 1 @@ -605,9 +601,8 @@ func (e *discoveredEntry) Weight() int64 { t := time.Duration(mclock.Now() - e.lastDiscovered) if t <= discoverExpireStart { return 1000000000 - } else { - return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) } + return int64(1000000000 * math.Exp(-float64(t-discoverExpireStart)/float64(discoverExpireConst))) } // knownEntry implements wrsItem diff --git a/les/sync.go b/les/sync.go index c0e17f97d9..1ac6455852 100644 --- a/les/sync.go +++ b/les/sync.go @@ -20,16 +20,11 @@ import ( "context" "time" - "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" ) -const ( - //forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available - minDesiredPeerCount = 5 // Amount of peers desired to start syncing -) - // syncer is responsible for periodically synchronising with the network, both // downloading hashes and blocks as well as handling the announcement handler. func (pm *ProtocolManager) syncer() { @@ -61,7 +56,7 @@ func (pm *ProtocolManager) syncer() { func (pm *ProtocolManager) needToSync(peerHead blockInfo) bool { head := pm.blockchain.CurrentHeader() - currentTd := core.GetTd(pm.chainDb, head.Hash(), head.Number.Uint64()) + currentTd := rawdb.ReadTd(pm.chainDb, head.Hash(), head.Number.Uint64()) return currentTd != nil && peerHead.Td.Cmp(currentTd) > 0 } diff --git a/light/lightchain.go b/light/lightchain.go index 2784615d35..9d0a4e4f73 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -142,7 +143,7 @@ func (self *LightChain) Odr() OdrBackend { // loadLastState loads the last known chain state from the database. This method // assumes that the chain manager mutex is held. func (self *LightChain) loadLastState() error { - if head := core.GetHeadHeaderHash(self.chainDb); head == (common.Hash{}) { + if head := rawdb.ReadHeadHeaderHash(self.chainDb); head == (common.Hash{}) { // Corrupt or empty database, init from scratch self.Reset() } else { @@ -189,12 +190,9 @@ func (bc *LightChain) ResetWithGenesisBlock(genesis *types.Block) { defer bc.mu.Unlock() // Prepare the genesis block and reinitialise the chain - if err := core.WriteTd(bc.chainDb, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()); err != nil { - log.Crit("Failed to write genesis block TD", "err", err) - } - if err := core.WriteBlock(bc.chainDb, genesis); err != nil { - log.Crit("Failed to write genesis block", "err", err) - } + rawdb.WriteTd(bc.chainDb, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) + rawdb.WriteBlock(bc.chainDb, genesis) + bc.genesisBlock = genesis bc.hc.SetGenesis(bc.genesisBlock.Header()) bc.hc.SetCurrentHeader(bc.genesisBlock.Header()) @@ -223,7 +221,11 @@ func (self *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.B body := cached.(*types.Body) return body, nil } - body, err := GetBody(ctx, self.odr, hash, self.hc.GetBlockNumber(hash)) + number := self.hc.GetBlockNumber(hash) + if number == nil { + return nil, errors.New("unknown block") + } + body, err := GetBody(ctx, self.odr, hash, *number) if err != nil { return nil, err } @@ -239,7 +241,11 @@ func (self *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.R if cached, ok := self.bodyRLPCache.Get(hash); ok { return cached.(rlp.RawValue), nil } - body, err := GetBodyRLP(ctx, self.odr, hash, self.hc.GetBlockNumber(hash)) + number := self.hc.GetBlockNumber(hash) + if number == nil { + return nil, errors.New("unknown block") + } + body, err := GetBodyRLP(ctx, self.odr, hash, *number) if err != nil { return nil, err } @@ -274,7 +280,11 @@ func (self *LightChain) GetBlock(ctx context.Context, hash common.Hash, number u // GetBlockByHash retrieves a block from the database or ODR service by hash, // caching it if found. func (self *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return self.GetBlock(ctx, hash, self.hc.GetBlockNumber(hash)) + number := self.hc.GetBlockNumber(hash) + if number == nil { + return nil, errors.New("unknown block") + } + return self.GetBlock(ctx, hash, *number) } // GetBlockByNumber retrieves a block from the database or ODR service by diff --git a/light/lightchain_test.go b/light/lightchain_test.go index 0af7551d41..c0aa51da29 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" @@ -51,7 +52,7 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [ // chain. Depending on the full flag, if creates either a full block chain or a // header only chain. func newCanonical(n int) (ethdb.Database, *LightChain, error) { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() gspec := core.Genesis{Config: params.TestChainConfig} genesis := gspec.MustCommit(db) blockchain, _ := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFaker()) @@ -68,7 +69,7 @@ func newCanonical(n int) (ethdb.Database, *LightChain, error) { // newTestLightChain creates a LightChain that doesn't validate anything. func newTestLightChain() *LightChain { - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() gspec := &core.Genesis{ Difficulty: big.NewInt(1), Config: params.TestChainConfig, @@ -122,8 +123,8 @@ func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error } // Manually insert the header into the database, but don't reorganize (allows subsequent testing) lightchain.mu.Lock() - core.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, lightchain.GetTdByHash(header.ParentHash))) - core.WriteHeader(lightchain.chainDb, header) + rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, lightchain.GetTdByHash(header.ParentHash))) + rawdb.WriteHeader(lightchain.chainDb, header) lightchain.mu.Unlock() } return nil diff --git a/light/odr.go b/light/odr.go index e2c3d9c5a4..8f1e50b817 100644 --- a/light/odr.go +++ b/light/odr.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" ) @@ -112,7 +113,7 @@ type BlockRequest struct { // StoreResult stores the retrieved data in local database func (req *BlockRequest) StoreResult(db ethdb.Database) { - core.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp) + rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp) } // ReceiptsRequest is the ODR request type for retrieving block bodies @@ -125,7 +126,7 @@ type ReceiptsRequest struct { // StoreResult stores the retrieved data in local database func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { - core.WriteBlockReceipts(db, req.Hash, req.Number, req.Receipts) + rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts) } // ChtRequest is the ODR request type for state/storage trie entries @@ -140,11 +141,11 @@ type ChtRequest struct { // StoreResult stores the retrieved data in local database func (req *ChtRequest) StoreResult(db ethdb.Database) { - // if there is a canonical hash, there is a header too - core.WriteHeader(db, req.Header) hash, num := req.Header.Hash(), req.Header.Number.Uint64() - core.WriteTd(db, hash, num, req.Td) - core.WriteCanonicalHash(db, hash, num) + + rawdb.WriteHeader(db, req.Header) + rawdb.WriteTd(db, hash, num, req.Td) + rawdb.WriteCanonicalHash(db, hash, num) } // BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure @@ -161,11 +162,11 @@ type BloomRequest struct { // StoreResult stores the retrieved data in local database func (req *BloomRequest) StoreResult(db ethdb.Database) { for i, sectionIdx := range req.SectionIdxList { - sectionHead := core.GetCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) + sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) // if we don't have the canonical hash stored for this section head number, we'll still store it under // a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the // bit vector again from the network. - core.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) + rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) } } diff --git a/light/odr_test.go b/light/odr_test.go index d3f9374fd8..3e7ac10118 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -70,9 +71,15 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { } switch req := req.(type) { case *BlockRequest: - req.Rlp = core.GetBodyRLP(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash)) + number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) + if number != nil { + req.Rlp = rawdb.ReadBodyRLP(odr.sdb, req.Hash, *number) + } case *ReceiptsRequest: - req.Receipts = core.GetBlockReceipts(odr.sdb, req.Hash, core.GetBlockNumber(odr.sdb, req.Hash)) + number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) + if number != nil { + req.Receipts = rawdb.ReadReceipts(odr.sdb, req.Hash, *number) + } case *TrieRequest: t, _ := trie.New(req.Id.Root, trie.NewDatabase(odr.sdb)) nodes := NewNodeSet() @@ -108,9 +115,15 @@ func TestOdrGetReceiptsLes1(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) } func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { var receipts types.Receipts if bc != nil { - receipts = core.GetBlockReceipts(db, bhash, core.GetBlockNumber(db, bhash)) + number := rawdb.ReadHeaderNumber(db, bhash) + if number != nil { + receipts = rawdb.ReadReceipts(db, bhash, *number) + } } else { - receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, core.GetBlockNumber(db, bhash)) + number := rawdb.ReadHeaderNumber(db, bhash) + if number != nil { + receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number) + } } if receipts == nil { return nil, nil @@ -232,8 +245,8 @@ func testChainGen(i int, block *core.BlockGen) { func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { var ( - sdb, _ = ethdb.NewMemDatabase() - ldb, _ = ethdb.NewMemDatabase() + sdb = ethdb.NewMemDatabase() + ldb = ethdb.NewMemDatabase() gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} genesis = gspec.MustCommit(sdb) ) @@ -260,7 +273,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { test := func(expFail int) { for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := core.GetCanonicalHash(sdb, i) + bhash := rawdb.ReadCanonicalHash(sdb, i) b1, err := fn(NoOdr, sdb, blockchain, nil, bhash) if err != nil { t.Fatalf("error in full-node test for block %d: %v", i, err) diff --git a/light/odr_util.go b/light/odr_util.go index d56330e36b..620af63835 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" @@ -31,10 +32,10 @@ var sha3_nil = crypto.Keccak256Hash(nil) func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) { db := odr.Database() - hash := core.GetCanonicalHash(db, number) + hash := rawdb.ReadCanonicalHash(db, number) if (hash != common.Hash{}) { // if there is a canonical hash, there is a header too - header := core.GetHeader(db, hash, number) + header := rawdb.ReadHeader(db, hash, number) if header == nil { panic("Canonical hash present but header not found") } @@ -47,14 +48,14 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ ) if odr.ChtIndexer() != nil { chtCount, sectionHeadNum, sectionHead = odr.ChtIndexer().Sections() - canonicalHash := core.GetCanonicalHash(db, sectionHeadNum) + canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum) // if the CHT was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too for chtCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) { chtCount-- if chtCount > 0 { sectionHeadNum = chtCount*CHTFrequencyClient - 1 sectionHead = odr.ChtIndexer().SectionHead(chtCount - 1) - canonicalHash = core.GetCanonicalHash(db, sectionHeadNum) + canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum) } } } @@ -69,7 +70,7 @@ func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*typ } func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) { - hash := core.GetCanonicalHash(odr.Database(), number) + hash := rawdb.ReadCanonicalHash(odr.Database(), number) if (hash != common.Hash{}) { return hash, nil } @@ -82,7 +83,7 @@ func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (commo // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) { - if data := core.GetBodyRLP(odr.Database(), hash, number); data != nil { + if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil { return data, nil } r := &BlockRequest{Hash: hash, Number: number} @@ -111,7 +112,7 @@ func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint6 // back from the stored header and body. func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) { // Retrieve the block header and body contents - header := core.GetHeader(odr.Database(), hash, number) + header := rawdb.ReadHeader(odr.Database(), hash, number) if header == nil { return nil, ErrNoHeader } @@ -127,7 +128,7 @@ func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint // in a block given by its hash. func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) { // Retrieve the potentially incomplete receipts from disk or network - receipts := core.GetBlockReceipts(odr.Database(), hash, number) + receipts := rawdb.ReadReceipts(odr.Database(), hash, number) if receipts == nil { r := &ReceiptsRequest{Hash: hash, Number: number} if err := odr.Retrieve(ctx, r); err != nil { @@ -141,13 +142,13 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num if err != nil { return nil, err } - genesis := core.GetCanonicalHash(odr.Database(), 0) - config, _ := core.GetChainConfig(odr.Database(), genesis) + genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) + config := rawdb.ReadChainConfig(odr.Database(), genesis) if err := core.SetReceiptsData(config, block, receipts); err != nil { return nil, err } - core.WriteBlockReceipts(odr.Database(), hash, number, receipts) + rawdb.WriteReceipts(odr.Database(), hash, number, receipts) } return receipts, nil } @@ -156,7 +157,7 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num // block given by its hash. func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) { // Retrieve the potentially incomplete receipts from disk or network - receipts := core.GetBlockReceipts(odr.Database(), hash, number) + receipts := rawdb.ReadReceipts(odr.Database(), hash, number) if receipts == nil { r := &ReceiptsRequest{Hash: hash, Number: number} if err := odr.Retrieve(ctx, r); err != nil { @@ -187,24 +188,24 @@ func GetBloomBits(ctx context.Context, odr OdrBackend, bitIdx uint, sectionIdxLi ) if odr.BloomTrieIndexer() != nil { bloomTrieCount, sectionHeadNum, sectionHead = odr.BloomTrieIndexer().Sections() - canonicalHash := core.GetCanonicalHash(db, sectionHeadNum) + canonicalHash := rawdb.ReadCanonicalHash(db, sectionHeadNum) // if the BloomTrie was injected as a trusted checkpoint, we have no canonical hash yet so we accept zero hash too for bloomTrieCount > 0 && canonicalHash != sectionHead && canonicalHash != (common.Hash{}) { bloomTrieCount-- if bloomTrieCount > 0 { sectionHeadNum = bloomTrieCount*BloomTrieFrequency - 1 sectionHead = odr.BloomTrieIndexer().SectionHead(bloomTrieCount - 1) - canonicalHash = core.GetCanonicalHash(db, sectionHeadNum) + canonicalHash = rawdb.ReadCanonicalHash(db, sectionHeadNum) } } } for i, sectionIdx := range sectionIdxList { - sectionHead := core.GetCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) + sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*BloomTrieFrequency-1) // if we don't have the canonical hash stored for this section head number, we'll still look for // an entry with a zero sectionHead (we store it with zero section head too if we don't know it // at the time of the retrieval) - bloomBits, err := core.GetBloomBits(db, bitIdx, sectionIdx, sectionHead) + bloomBits, err := rawdb.ReadBloomBits(db, bitIdx, sectionIdx, sectionHead) if err == nil { result[i] = bloomBits } else { diff --git a/light/postprocess.go b/light/postprocess.go index a1b1d9fb04..c06c18027e 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/bitutil" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -58,18 +59,18 @@ type trustedCheckpoint struct { var ( mainnetCheckpoint = trustedCheckpoint{ name: "mainnet", - sectionIdx: 161, - sectionHead: common.HexToHash("75b0c4baa7a62cece48abdcb03b6f31601961c9bece67dcd61df87aad4fc0d8d"), - chtRoot: common.HexToHash("bbbfaa67b29716348997ec21a39c03b8d1fb973f6a43740b865595ba26ee812f"), - bloomTrieRoot: common.HexToHash("d6db6e6248354d7453391ce97830072a28ea4216be0bd95a5db9f53b1a64677b"), + sectionIdx: 170, + sectionHead: common.HexToHash("3bb2c28bcce463d57968f14f56cdb3fbf35349ab7a701f44c1afb57349c9a356"), + chtRoot: common.HexToHash("d92b6d0853455f8439086292338e87f69781921680dd7aa072fb71547b87415e"), + bloomTrieRoot: common.HexToHash("e4e8250a2fefddead7ae42daecd848cbf9b66d748a8270f8bbd4370b764bb9e9"), } ropstenCheckpoint = trustedCheckpoint{ name: "ropsten", - sectionIdx: 87, - sectionHead: common.HexToHash("ebc0adcb30ed21cbe95bd77499cc1af0bada621fee3644cb80dbcf1444c123fe"), - chtRoot: common.HexToHash("d9830f4893c821ddf149b8cb9d3e3bfe3109d2eea8e3c4a4ede7c8b2ee8a7800"), - bloomTrieRoot: common.HexToHash("c76e12d713f65b84c5a36d06bc77d0c8419248ea0b36e0812a78b76aa6da0ddb"), + sectionIdx: 97, + sectionHead: common.HexToHash("719448c67c01eb5b9f27833a36a4e34612f66801316d7ff37daf9e77fb4cd095"), + chtRoot: common.HexToHash("a7857afc15930ca6e583b6c3d563a025144011655843d52d28e2fdaadd417bea"), + bloomTrieRoot: common.HexToHash("9c71d4b50cbec86dfeaa8e08992de8a4667b81d13c54d6522b17ce2fc5d36416"), } ) @@ -161,7 +162,7 @@ func (c *ChtIndexerBackend) Process(header *types.Header) { hash, num := header.Hash(), header.Number.Uint64() c.lastHash = hash - td := core.GetTd(c.diskdb, hash, num) + td := rawdb.ReadTd(c.diskdb, hash, num) if td == nil { panic(nil) } @@ -272,7 +273,7 @@ func (b *BloomTrieIndexerBackend) Commit() error { binary.BigEndian.PutUint64(encKey[2:10], b.section) var decomp []byte for j := uint64(0); j < b.bloomTrieRatio; j++ { - data, err := core.GetBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) + data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) if err != nil { return err } diff --git a/light/trie_test.go b/light/trie_test.go index 0d6b2cc1d8..84c6f162fb 100644 --- a/light/trie_test.go +++ b/light/trie_test.go @@ -34,10 +34,10 @@ import ( func TestNodeIterator(t *testing.T) { var ( - fulldb, _ = ethdb.NewMemDatabase() - lightdb, _ = ethdb.NewMemDatabase() - gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} - genesis = gspec.MustCommit(fulldb) + fulldb = ethdb.NewMemDatabase() + lightdb = ethdb.NewMemDatabase() + gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} + genesis = gspec.MustCommit(fulldb) ) gspec.MustCommit(lightdb) blockchain, _ := core.NewBlockChain(fulldb, nil, params.TestChainConfig, ethash.NewFullFaker(), vm.Config{}) diff --git a/light/txpool.go b/light/txpool.go index ca41490bdc..94c8139cbd 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -183,9 +184,8 @@ func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results return err } - if err := core.WriteTxLookupEntries(pool.chainDb, block); err != nil { - return err - } + rawdb.WriteTxLookupEntries(pool.chainDb, block) + // Update the transaction pool's state for _, tx := range list { delete(pool.pending, tx.Hash()) @@ -202,7 +202,7 @@ func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) { if list, ok := pool.mined[hash]; ok { for _, tx := range list { txHash := tx.Hash() - core.DeleteTxLookupEntry(pool.chainDb, txHash) + rawdb.DeleteTxLookupEntry(pool.chainDb, txHash) pool.pending[txHash] = tx txc.setState(txHash, false) } @@ -258,7 +258,7 @@ func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) idx2 := idx - txPermanent if len(pool.mined) > 0 { for i := pool.clearIdx; i < idx2; i++ { - hash := core.GetCanonicalHash(pool.chainDb, i) + hash := rawdb.ReadCanonicalHash(pool.chainDb, i) if list, ok := pool.mined[hash]; ok { hashes := make([]common.Hash, len(list)) for i, tx := range list { diff --git a/light/txpool_test.go b/light/txpool_test.go index 13d7d3cebb..ccbd83a943 100644 --- a/light/txpool_test.go +++ b/light/txpool_test.go @@ -81,8 +81,8 @@ func TestTxPool(t *testing.T) { } var ( - sdb, _ = ethdb.NewMemDatabase() - ldb, _ = ethdb.NewMemDatabase() + sdb = ethdb.NewMemDatabase() + ldb = ethdb.NewMemDatabase() gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} genesis = gspec.MustCommit(sdb) ) diff --git a/log/handler.go b/log/handler.go index d5594b853d..41d5718ddb 100644 --- a/log/handler.go +++ b/log/handler.go @@ -234,9 +234,8 @@ func FailoverHandler(hs ...Handler) Handler { err = h.Log(r) if err == nil { return nil - } else { - r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) } + r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) } return err @@ -320,13 +319,12 @@ func evaluateLazy(lz Lazy) (interface{}, error) { results := value.Call([]reflect.Value{}) if len(results) == 1 { return results[0].Interface(), nil - } else { - values := make([]interface{}, len(results)) - for i, v := range results { - values[i] = v.Interface() - } - return values, nil } + values := make([]interface{}, len(results)) + for i, v := range results { + values[i] = v.Interface() + } + return values, nil } // DiscardHandler reports success for all writes but does nothing. diff --git a/metrics/librato/client.go b/metrics/librato/client.go index 8c0c850e38..1f8920cb1a 100644 --- a/metrics/librato/client.go +++ b/metrics/librato/client.go @@ -65,7 +65,7 @@ type Batch struct { Source string `json:"source"` } -func (self *LibratoClient) PostMetrics(batch Batch) (err error) { +func (c *LibratoClient) PostMetrics(batch Batch) (err error) { var ( js []byte req *http.Request @@ -85,7 +85,7 @@ func (self *LibratoClient) PostMetrics(batch Batch) (err error) { } req.Header.Set("Content-Type", "application/json") - req.SetBasicAuth(self.Email, self.Token) + req.SetBasicAuth(c.Email, c.Token) if resp, err = http.DefaultClient.Do(req); err != nil { return diff --git a/metrics/librato/librato.go b/metrics/librato/librato.go index f8c8c9ecb7..2138e01ae8 100644 --- a/metrics/librato/librato.go +++ b/metrics/librato/librato.go @@ -40,14 +40,14 @@ func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, NewReporter(r, d, e, t, s, p, u).Run() } -func (self *Reporter) Run() { +func (rep *Reporter) Run() { log.Printf("WARNING: This client has been DEPRECATED! It has been moved to https://github.com/mihasya/go-metrics-librato and will be removed from rcrowley/go-metrics on August 5th 2015") - ticker := time.Tick(self.Interval) - metricsApi := &LibratoClient{self.Email, self.Token} + ticker := time.Tick(rep.Interval) + metricsApi := &LibratoClient{rep.Email, rep.Token} for now := range ticker { var metrics Batch var err error - if metrics, err = self.BuildRequest(now, self.Registry); err != nil { + if metrics, err = rep.BuildRequest(now, rep.Registry); err != nil { log.Printf("ERROR constructing librato request body %s", err) continue } @@ -79,21 +79,21 @@ func sumSquaresTimer(t metrics.Timer) float64 { return sumSquares } -func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) { +func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) { snapshot = Batch{ // coerce timestamps to a stepping fn so that they line up in Librato graphs - MeasureTime: (now.Unix() / self.intervalSec) * self.intervalSec, - Source: self.Source, + MeasureTime: (now.Unix() / rep.intervalSec) * rep.intervalSec, + Source: rep.Source, } snapshot.Gauges = make([]Measurement, 0) snapshot.Counters = make([]Measurement, 0) - histogramGaugeCount := 1 + len(self.Percentiles) + histogramGaugeCount := 1 + len(rep.Percentiles) r.Each(func(name string, metric interface{}) { - if self.Namespace != "" { - name = fmt.Sprintf("%s.%s", self.Namespace, name) + if rep.Namespace != "" { + name = fmt.Sprintf("%s.%s", rep.Namespace, name) } measurement := Measurement{} - measurement[Period] = self.Interval.Seconds() + measurement[Period] = rep.Interval.Seconds() switch m := metric.(type) { case metrics.Counter: if m.Count() > 0 { @@ -125,7 +125,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot measurement[Sum] = float64(s.Sum()) measurement[SumSquares] = sumSquares(s) gauges[0] = measurement - for i, p := range self.Percentiles { + for i, p := range rep.Percentiles { gauges[i+1] = Measurement{ Name: fmt.Sprintf("%s.%.2f", measurement[Name], p), Value: s.Percentile(p), @@ -142,7 +142,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Measurement{ Name: fmt.Sprintf("%s.%s", name, "1min"), Value: m.Rate1(), - Period: int64(self.Interval.Seconds()), + Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -152,7 +152,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Measurement{ Name: fmt.Sprintf("%s.%s", name, "5min"), Value: m.Rate5(), - Period: int64(self.Interval.Seconds()), + Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -162,7 +162,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Measurement{ Name: fmt.Sprintf("%s.%s", name, "15min"), Value: m.Rate15(), - Period: int64(self.Interval.Seconds()), + Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -184,15 +184,15 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Max: float64(m.Max()), Min: float64(m.Min()), SumSquares: sumSquaresTimer(m), - Period: int64(self.Interval.Seconds()), - Attributes: self.TimerAttributes, + Period: int64(rep.Interval.Seconds()), + Attributes: rep.TimerAttributes, } - for i, p := range self.Percentiles { + for i, p := range rep.Percentiles { gauges[i+1] = Measurement{ Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100), Value: m.Percentile(p), - Period: int64(self.Interval.Seconds()), - Attributes: self.TimerAttributes, + Period: int64(rep.Interval.Seconds()), + Attributes: rep.TimerAttributes, } } snapshot.Gauges = append(snapshot.Gauges, gauges...) @@ -200,7 +200,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Measurement{ Name: fmt.Sprintf("%s.%s", name, "rate.1min"), Value: m.Rate1(), - Period: int64(self.Interval.Seconds()), + Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -210,7 +210,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Measurement{ Name: fmt.Sprintf("%s.%s", name, "rate.5min"), Value: m.Rate5(), - Period: int64(self.Interval.Seconds()), + Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -220,7 +220,7 @@ func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Measurement{ Name: fmt.Sprintf("%s.%s", name, "rate.15min"), Value: m.Rate15(), - Period: int64(self.Interval.Seconds()), + Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, diff --git a/miner/worker.go b/miner/worker.go index 15395ae0b9..48b0b27652 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -117,6 +117,10 @@ type worker struct { currentMu sync.Mutex current *Work + snapshotMu sync.RWMutex + snapshotBlock *types.Block + snapshotState *state.StateDB + uncleMu sync.Mutex possibleUncles map[common.Hash]*types.Block @@ -171,32 +175,28 @@ func (self *worker) setExtra(extra []byte) { } func (self *worker) pending() (*types.Block, *state.StateDB) { + if atomic.LoadInt32(&self.mining) == 0 { + // return a snapshot to avoid contention on currentMu mutex + self.snapshotMu.RLock() + defer self.snapshotMu.RUnlock() + return self.snapshotBlock, self.snapshotState.Copy() + } + self.currentMu.Lock() defer self.currentMu.Unlock() - - if atomic.LoadInt32(&self.mining) == 0 { - return types.NewBlock( - self.current.header, - self.current.txs, - nil, - self.current.receipts, - ), self.current.state.Copy() - } return self.current.Block, self.current.state.Copy() } func (self *worker) pendingBlock() *types.Block { + if atomic.LoadInt32(&self.mining) == 0 { + // return a snapshot to avoid contention on currentMu mutex + self.snapshotMu.RLock() + defer self.snapshotMu.RUnlock() + return self.snapshotBlock + } + self.currentMu.Lock() defer self.currentMu.Unlock() - - if atomic.LoadInt32(&self.mining) == 0 { - return types.NewBlock( - self.current.header, - self.current.txs, - nil, - self.current.receipts, - ) - } return self.current.Block } @@ -268,6 +268,7 @@ func (self *worker) update() { txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs) self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase) + self.updateSnapshot() self.currentMu.Unlock() } else { // If we're mining, but nothing is being processed, wake on new transactions @@ -489,6 +490,7 @@ func (self *worker) commitNewWork() { self.unconfirmed.Shift(work.Block.NumberU64() - 1) } self.push(work) + self.updateSnapshot() } func (self *worker) commitUncle(work *Work, uncle *types.Header) error { @@ -506,6 +508,19 @@ func (self *worker) commitUncle(work *Work, uncle *types.Header) error { return nil } +func (self *worker) updateSnapshot() { + self.snapshotMu.Lock() + defer self.snapshotMu.Unlock() + + self.snapshotBlock = types.NewBlock( + self.current.header, + self.current.txs, + nil, + self.current.receipts, + ) + self.snapshotState = self.current.state.Copy() +} + func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) { gp := new(core.GasPool).AddGas(env.header.GasLimit) diff --git a/mobile/geth.go b/mobile/geth.go index 488a4150fc..645b360eb3 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -72,6 +73,9 @@ type NodeConfig struct { // WhisperEnabled specifies whether the node should run the Whisper protocol. WhisperEnabled bool + + // Listening address of pprof server. + PprofAddress string } // defaultNodeConfig contains the default node configuration values to use if all @@ -107,6 +111,11 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { if config.BootstrapNodes == nil || config.BootstrapNodes.Size() == 0 { config.BootstrapNodes = defaultNodeConfig.BootstrapNodes } + + if config.PprofAddress != "" { + debug.StartPProf(config.PprofAddress) + } + // Create the empty networking stack nodeConf := &node.Config{ Name: clientIdentifier, @@ -127,6 +136,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { return nil, err } + debug.Memsize.Add("node", rawStack) + var genesis *core.Genesis if config.EthereumGenesis != "" { // Parse the user supplied genesis spec if not mainnet diff --git a/mobile/types.go b/mobile/types.go index 24cd7ebf11..f32b4918f3 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -80,7 +80,7 @@ func (h *Header) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(h.header) } -// NewHeaderFromJSON parses a header from an JSON data dump. +// NewHeaderFromJSON parses a header from a JSON data dump. func NewHeaderFromJSON(data string) (*Header, error) { h := &Header{ header: new(types.Header), @@ -91,7 +91,7 @@ func NewHeaderFromJSON(data string) (*Header, error) { return h, nil } -// EncodeJSON encodes a header into an JSON data dump. +// EncodeJSON encodes a header into a JSON data dump. func (h *Header) EncodeJSON() (string, error) { data, err := json.Marshal(h.header) return string(data), err @@ -151,7 +151,7 @@ func (b *Block) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(b.block) } -// NewBlockFromJSON parses a block from an JSON data dump. +// NewBlockFromJSON parses a block from a JSON data dump. func NewBlockFromJSON(data string) (*Block, error) { b := &Block{ block: new(types.Block), @@ -162,7 +162,7 @@ func NewBlockFromJSON(data string) (*Block, error) { return b, nil } -// EncodeJSON encodes a block into an JSON data dump. +// EncodeJSON encodes a block into a JSON data dump. func (b *Block) EncodeJSON() (string, error) { data, err := json.Marshal(b.block) return string(data), err @@ -220,7 +220,7 @@ func (tx *Transaction) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(tx.tx) } -// NewTransactionFromJSON parses a transaction from an JSON data dump. +// NewTransactionFromJSON parses a transaction from a JSON data dump. func NewTransactionFromJSON(data string) (*Transaction, error) { tx := &Transaction{ tx: new(types.Transaction), @@ -231,7 +231,7 @@ func NewTransactionFromJSON(data string) (*Transaction, error) { return tx, nil } -// EncodeJSON encodes a transaction into an JSON data dump. +// EncodeJSON encodes a transaction into a JSON data dump. func (tx *Transaction) EncodeJSON() (string, error) { data, err := json.Marshal(tx.tx) return string(data), err @@ -312,7 +312,7 @@ func (r *Receipt) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(r.receipt) } -// NewReceiptFromJSON parses a transaction receipt from an JSON data dump. +// NewReceiptFromJSON parses a transaction receipt from a JSON data dump. func NewReceiptFromJSON(data string) (*Receipt, error) { r := &Receipt{ receipt: new(types.Receipt), @@ -323,12 +323,13 @@ func NewReceiptFromJSON(data string) (*Receipt, error) { return r, nil } -// EncodeJSON encodes a transaction receipt into an JSON data dump. +// EncodeJSON encodes a transaction receipt into a JSON data dump. func (r *Receipt) EncodeJSON() (string, error) { data, err := rlp.EncodeToBytes(r.receipt) return string(data), err } +func (r *Receipt) GetStatus() int { return int(r.receipt.Status) } func (r *Receipt) GetPostState() []byte { return r.receipt.PostState } func (r *Receipt) GetCumulativeGasUsed() int64 { return int64(r.receipt.CumulativeGasUsed) } func (r *Receipt) GetBloom() *Bloom { return &Bloom{r.receipt.Bloom} } diff --git a/node/config.go b/node/config.go index dda24583ee..486eddf925 100644 --- a/node/config.go +++ b/node/config.go @@ -209,7 +209,7 @@ func DefaultHTTPEndpoint() string { return config.HTTPEndpoint() } -// WSEndpoint resolves an websocket endpoint based on the configured host interface +// WSEndpoint resolves a websocket endpoint based on the configured host interface // and port parameters. func (c *Config) WSEndpoint() string { if c.WSHost == "" { diff --git a/node/node.go b/node/node.go index b02aecfad1..c4368189f7 100644 --- a/node/node.go +++ b/node/node.go @@ -303,50 +303,16 @@ func (n *Node) stopInProc() { // startIPC initializes and starts the IPC RPC endpoint. func (n *Node) startIPC(apis []rpc.API) error { - // Short circuit if the IPC endpoint isn't being exposed if n.ipcEndpoint == "" { - return nil + return nil // IPC disabled. } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("IPC registered", "service", api.Service, "namespace", api.Namespace) - } - // All APIs registered, start the IPC listener - var ( - listener net.Listener - err error - ) - if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil { + listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis) + if err != nil { return err } - go func() { - n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) - - for { - conn, err := listener.Accept() - if err != nil { - // Terminate if the listener was closed - n.lock.RLock() - closed := n.ipcListener == nil - n.lock.RUnlock() - if closed { - return - } - // Not closed, just some error; report and continue - n.log.Error("IPC accept failed", "err", err) - continue - } - go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) - } - }() - // All listeners booted successfully n.ipcListener = listener n.ipcHandler = handler - + n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) return nil } @@ -370,30 +336,10 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors if endpoint == "" { return nil } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("HTTP registered", "service", api.Service, "namespace", api.Namespace) - } - } - // All APIs registered, start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { + listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts) + if err != nil { return err } - go rpc.NewHTTPServer(cors, vhosts, handler).Serve(listener) n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) // All listeners booted successfully n.httpEndpoint = endpoint @@ -423,32 +369,11 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig if endpoint == "" { return nil } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("WebSocket registered", "service", api.Service, "namespace", api.Namespace) - } - } - // All APIs registered, start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { + listener, handler, err := rpc.StartWSEndpoint(endpoint, apis, modules, wsOrigins, exposeAll) + if err != nil { return err } - go rpc.NewWSServer(wsOrigins, handler).Serve(listener) n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) - // All listeners booted successfully n.wsEndpoint = endpoint n.wsListener = listener @@ -643,7 +568,7 @@ func (n *Node) EventMux() *event.TypeMux { // ephemeral, a memory database is returned. func (n *Node) OpenDatabase(name string, cache, handles int) (ethdb.Database, error) { if n.config.DataDir == "" { - return ethdb.NewMemDatabase() + return ethdb.NewMemDatabase(), nil } return ethdb.NewLDBDatabase(n.config.resolvePath(name), cache, handles) } diff --git a/node/service.go b/node/service.go index 55062a5004..afc43e8486 100644 --- a/node/service.go +++ b/node/service.go @@ -41,7 +41,7 @@ type ServiceContext struct { // node is an ephemeral one, a memory database is returned. func (ctx *ServiceContext) OpenDatabase(name string, cache int, handles int) (ethdb.Database, error) { if ctx.config.DataDir == "" { - return ethdb.NewMemDatabase() + return ethdb.NewMemDatabase(), nil } db, err := ethdb.NewLDBDatabase(ctx.config.resolvePath(name), cache, handles) if err != nil { diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 3ce48d2995..f2d3f9a2ad 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -582,26 +582,26 @@ func (*preminedTestnet) ping(toid NodeID, toaddr *net.UDPAddr) error { return ni // mine generates a testnet struct literal with nodes at // various distances to the given target. -func (n *preminedTestnet) mine(target NodeID) { - n.target = target - n.targetSha = crypto.Keccak256Hash(n.target[:]) +func (tn *preminedTestnet) mine(target NodeID) { + tn.target = target + tn.targetSha = crypto.Keccak256Hash(tn.target[:]) found := 0 for found < bucketSize*10 { k := newkey() id := PubkeyID(&k.PublicKey) sha := crypto.Keccak256Hash(id[:]) - ld := logdist(n.targetSha, sha) - if len(n.dists[ld]) < bucketSize { - n.dists[ld] = append(n.dists[ld], id) + ld := logdist(tn.targetSha, sha) + if len(tn.dists[ld]) < bucketSize { + tn.dists[ld] = append(tn.dists[ld], id) fmt.Println("found ID with ld", ld) found++ } } fmt.Println("&preminedTestnet{") - fmt.Printf(" target: %#v,\n", n.target) - fmt.Printf(" targetSha: %#v,\n", n.targetSha) - fmt.Printf(" dists: [%d][]NodeID{\n", len(n.dists)) - for ld, ns := range n.dists { + fmt.Printf(" target: %#v,\n", tn.target) + fmt.Printf(" targetSha: %#v,\n", tn.targetSha) + fmt.Printf(" dists: [%d][]NodeID{\n", len(tn.dists)) + for ld, ns := range tn.dists { if len(ns) == 0 { continue } diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 524c6e4988..f6bcd97085 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -49,7 +49,6 @@ var ( // Timeouts const ( respTimeout = 500 * time.Millisecond - sendTimeout = 500 * time.Millisecond expiration = 20 * time.Second ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index 52c677b623..9b0bd0c80a 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -36,7 +36,6 @@ import ( var ( errInvalidEvent = errors.New("invalid in current state") errNoQuery = errors.New("no pending query") - errWrongAddress = errors.New("unknown sender address") ) const ( @@ -828,11 +827,10 @@ type nodeEvent uint //go:generate stringer -type=nodeEvent const ( - invalidEvent nodeEvent = iota // zero is reserved // Packet type events. // These correspond to packet types in the UDP protocol. - pingPacket + pingPacket = iota + 1 pongPacket findnodePacket neighborsPacket diff --git a/p2p/discv5/net_test.go b/p2p/discv5/net_test.go index 369282ca9c..001d193cc9 100644 --- a/p2p/discv5/net_test.go +++ b/p2p/discv5/net_test.go @@ -336,26 +336,26 @@ func (*preminedTestnet) localAddr() *net.UDPAddr { // mine generates a testnet struct literal with nodes at // various distances to the given target. -func (n *preminedTestnet) mine(target NodeID) { - n.target = target - n.targetSha = crypto.Keccak256Hash(n.target[:]) +func (tn *preminedTestnet) mine(target NodeID) { + tn.target = target + tn.targetSha = crypto.Keccak256Hash(tn.target[:]) found := 0 for found < bucketSize*10 { k := newkey() id := PubkeyID(&k.PublicKey) sha := crypto.Keccak256Hash(id[:]) - ld := logdist(n.targetSha, sha) - if len(n.dists[ld]) < bucketSize { - n.dists[ld] = append(n.dists[ld], id) + ld := logdist(tn.targetSha, sha) + if len(tn.dists[ld]) < bucketSize { + tn.dists[ld] = append(tn.dists[ld], id) fmt.Println("found ID with ld", ld) found++ } } fmt.Println("&preminedTestnet{") - fmt.Printf(" target: %#v,\n", n.target) - fmt.Printf(" targetSha: %#v,\n", n.targetSha) - fmt.Printf(" dists: [%d][]NodeID{\n", len(n.dists)) - for ld, ns := range n.dists { + fmt.Printf(" target: %#v,\n", tn.target) + fmt.Printf(" targetSha: %#v,\n", tn.targetSha) + fmt.Printf(" dists: [%d][]NodeID{\n", len(tn.dists)) + for ld, ns := range tn.dists { if len(ns) == 0 { continue } diff --git a/p2p/discv5/node.go b/p2p/discv5/node.go index fd88a55b15..3d47485122 100644 --- a/p2p/discv5/node.go +++ b/p2p/discv5/node.go @@ -315,11 +315,11 @@ func PubkeyID(pub *ecdsa.PublicKey) NodeID { // Pubkey returns the public key represented by the node ID. // It returns an error if the ID is not a point on the curve. -func (id NodeID) Pubkey() (*ecdsa.PublicKey, error) { +func (n NodeID) Pubkey() (*ecdsa.PublicKey, error) { p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} - half := len(id) / 2 - p.X.SetBytes(id[:half]) - p.Y.SetBytes(id[half:]) + half := len(n) / 2 + p.X.SetBytes(n[:half]) + p.Y.SetBytes(n[half:]) if !p.Curve.IsOnCurve(p.X, p.Y) { return nil, errors.New("id is invalid secp256k1 curve point") } diff --git a/p2p/discv5/nodeevent_string.go b/p2p/discv5/nodeevent_string.go index eb696fb8be..38c1993bac 100644 --- a/p2p/discv5/nodeevent_string.go +++ b/p2p/discv5/nodeevent_string.go @@ -4,24 +4,14 @@ package discv5 import "strconv" -const ( - _nodeEvent_name_0 = "invalidEventpingPacketpongPacketfindnodePacketneighborsPacketfindnodeHashPackettopicRegisterPackettopicQueryPackettopicNodesPacket" - _nodeEvent_name_1 = "pongTimeoutpingTimeoutneighboursTimeout" -) +const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout" -var ( - _nodeEvent_index_0 = [...]uint8{0, 12, 22, 32, 46, 61, 79, 98, 114, 130} - _nodeEvent_index_1 = [...]uint8{0, 11, 22, 39} -) +var _nodeEvent_index = [...]uint8{0, 11, 22, 39} func (i nodeEvent) String() string { - switch { - case 0 <= i && i <= 8: - return _nodeEvent_name_0[_nodeEvent_index_0[i]:_nodeEvent_index_0[i+1]] - case 265 <= i && i <= 267: - i -= 265 - return _nodeEvent_name_1[_nodeEvent_index_1[i]:_nodeEvent_index_1[i+1]] - default: - return "nodeEvent(" + strconv.FormatInt(int64(i), 10) + ")" + i -= 264 + if i >= nodeEvent(len(_nodeEvent_index)-1) { + return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")" } + return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]] } diff --git a/p2p/discv5/table.go b/p2p/discv5/table.go index 2cf05009cb..c8d234b936 100644 --- a/p2p/discv5/table.go +++ b/p2p/discv5/table.go @@ -38,7 +38,6 @@ const ( hashBits = len(common.Hash{}) * 8 nBuckets = hashBits + 1 // Number of buckets - maxBondingPingPongs = 16 maxFindnodeFailures = 5 ) diff --git a/p2p/discv5/ticket.go b/p2p/discv5/ticket.go index b3d1ac4baf..ae4b18e7cd 100644 --- a/p2p/discv5/ticket.go +++ b/p2p/discv5/ticket.go @@ -304,8 +304,8 @@ func (s ticketRefByWaitTime) Len() int { return len(s) } -func (r ticketRef) waitTime() mclock.AbsTime { - return r.t.regTime[r.idx] - r.t.issueTime +func (ref ticketRef) waitTime() mclock.AbsTime { + return ref.t.regTime[ref.idx] - ref.t.issueTime } // Less reports whether the element with diff --git a/p2p/discv5/topic.go b/p2p/discv5/topic.go index e7a7f8e02a..609a41297f 100644 --- a/p2p/discv5/topic.go +++ b/p2p/discv5/topic.go @@ -271,15 +271,15 @@ func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx return false } -func (topictab *topicTable) getTicket(node *Node, topics []Topic) *ticket { - topictab.collectGarbage() +func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket { + t.collectGarbage() now := mclock.Now() - n := topictab.getOrNewNode(node) + n := t.getOrNewNode(node) n.lastIssuedTicket++ - topictab.storeTicketCounters(node) + t.storeTicketCounters(node) - t := &ticket{ + tic := &ticket{ issueTime: now, topics: topics, serial: n.lastIssuedTicket, @@ -287,15 +287,15 @@ func (topictab *topicTable) getTicket(node *Node, topics []Topic) *ticket { } for i, topic := range topics { var waitPeriod time.Duration - if topic := topictab.topics[topic]; topic != nil { + if topic := t.topics[topic]; topic != nil { waitPeriod = topic.wcl.waitPeriod } else { waitPeriod = minWaitPeriod } - t.regTime[i] = now + mclock.AbsTime(waitPeriod) + tic.regTime[i] = now + mclock.AbsTime(waitPeriod) } - return t + return tic } const gcInterval = time.Minute diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go index 6ce72d2c15..09e5f8b374 100644 --- a/p2p/discv5/udp.go +++ b/p2p/discv5/udp.go @@ -36,25 +36,17 @@ const Version = 4 // Errors var ( - errPacketTooSmall = errors.New("too small") - errBadPrefix = errors.New("bad prefix") - errExpired = errors.New("expired") - errUnsolicitedReply = errors.New("unsolicited reply") - errUnknownNode = errors.New("unknown node") - errTimeout = errors.New("RPC timeout") - errClockWarp = errors.New("reply deadline too far in the future") - errClosed = errors.New("socket closed") + errPacketTooSmall = errors.New("too small") + errBadPrefix = errors.New("bad prefix") + errTimeout = errors.New("RPC timeout") ) // Timeouts const ( respTimeout = 500 * time.Millisecond - queryDelay = 1000 * time.Millisecond expiration = 20 * time.Second - ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP - ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning - driftThreshold = 10 * time.Second // Allowed clock drift before warning user + driftThreshold = 10 * time.Second // Allowed clock drift before warning user ) // RPC request structures diff --git a/p2p/discv5/udp_test.go b/p2p/discv5/udp_test.go index 7d31815947..62184aa9d3 100644 --- a/p2p/discv5/udp_test.go +++ b/p2p/discv5/udp_test.go @@ -24,7 +24,6 @@ import ( "reflect" "sync" "testing" - "time" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" @@ -38,11 +37,7 @@ func init() { // shared test variables var ( - futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) - testTarget = NodeID{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} - testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} - testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} - testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} + testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} ) // type udpTest struct { diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index 2c3afb43e9..c018895cc0 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -46,7 +46,6 @@ const ID_SECP256k1_KECCAK = ID("secp256k1-keccak") // the default identity schem var ( errNoID = errors.New("unknown or unspecified identity scheme") - errInvalidSigsize = errors.New("invalid signature size") errInvalidSig = errors.New("invalid signature") errNotSorted = errors.New("record key/value pairs are not sorted by key") errDuplicateKey = errors.New("record contains duplicate key") diff --git a/p2p/message.go b/p2p/message.go index 50b419970d..a4eac54d33 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -229,21 +229,20 @@ func ExpectMsg(r MsgReader, code uint64, content interface{}) error { } if content == nil { return msg.Discard() - } else { - contentEnc, err := rlp.EncodeToBytes(content) - if err != nil { - panic("content encode error: " + err.Error()) - } - if int(msg.Size) != len(contentEnc) { - return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc)) - } - actualContent, err := ioutil.ReadAll(msg.Payload) - if err != nil { - return err - } - if !bytes.Equal(actualContent, contentEnc) { - return fmt.Errorf("message payload mismatch:\ngot: %x\nwant: %x", actualContent, contentEnc) - } + } + contentEnc, err := rlp.EncodeToBytes(content) + if err != nil { + panic("content encode error: " + err.Error()) + } + if int(msg.Size) != len(contentEnc) { + return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc)) + } + actualContent, err := ioutil.ReadAll(msg.Payload) + if err != nil { + return err + } + if !bytes.Equal(actualContent, contentEnc) { + return fmt.Errorf("message payload mismatch:\ngot: %x\nwant: %x", actualContent, contentEnc) } return nil } @@ -271,15 +270,15 @@ func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID discover.NodeID, p // ReadMsg reads a message from the underlying MsgReadWriter and emits a // "message received" event -func (self *msgEventer) ReadMsg() (Msg, error) { - msg, err := self.MsgReadWriter.ReadMsg() +func (ev *msgEventer) ReadMsg() (Msg, error) { + msg, err := ev.MsgReadWriter.ReadMsg() if err != nil { return msg, err } - self.feed.Send(&PeerEvent{ + ev.feed.Send(&PeerEvent{ Type: PeerEventTypeMsgRecv, - Peer: self.peerID, - Protocol: self.Protocol, + Peer: ev.peerID, + Protocol: ev.Protocol, MsgCode: &msg.Code, MsgSize: &msg.Size, }) @@ -288,15 +287,15 @@ func (self *msgEventer) ReadMsg() (Msg, error) { // WriteMsg writes a message to the underlying MsgReadWriter and emits a // "message sent" event -func (self *msgEventer) WriteMsg(msg Msg) error { - err := self.MsgReadWriter.WriteMsg(msg) +func (ev *msgEventer) WriteMsg(msg Msg) error { + err := ev.MsgReadWriter.WriteMsg(msg) if err != nil { return err } - self.feed.Send(&PeerEvent{ + ev.feed.Send(&PeerEvent{ Type: PeerEventTypeMsgSend, - Peer: self.peerID, - Protocol: self.Protocol, + Peer: ev.peerID, + Protocol: ev.Protocol, MsgCode: &msg.Code, MsgSize: &msg.Size, }) @@ -305,8 +304,8 @@ func (self *msgEventer) WriteMsg(msg Msg) error { // Close closes the underlying MsgReadWriter if it implements the io.Closer // interface -func (self *msgEventer) Close() error { - if v, ok := self.MsgReadWriter.(io.Closer); ok { +func (ev *msgEventer) Close() error { + if v, ok := ev.MsgReadWriter.(io.Closer); ok { return v.Close() } return nil diff --git a/p2p/peer.go b/p2p/peer.go index 477d8c2190..c3907349fc 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -47,8 +47,6 @@ const ( discMsg = 0x01 pingMsg = 0x02 pongMsg = 0x03 - getPeersMsg = 0x04 - peersMsg = 0x05 ) // protoHandshake is the RLP structure of the protocol handshake. @@ -222,6 +220,7 @@ loop: reason = discReasonForError(err) break loop case err = <-p.disc: + reason = discReasonForError(err) break loop } } diff --git a/p2p/peer_error.go b/p2p/peer_error.go index a1cddb707b..ab61bfef06 100644 --- a/p2p/peer_error.go +++ b/p2p/peer_error.go @@ -48,8 +48,8 @@ func newPeerError(code int, format string, v ...interface{}) *peerError { return err } -func (self *peerError) Error() string { - return self.message +func (pe *peerError) Error() string { + return pe.message } var errProtocolReturned = errors.New("protocol returned") diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index a566fb27d8..f381c11596 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -17,7 +17,6 @@ package adapters import ( - "bufio" "context" "crypto/ecdsa" "encoding/json" @@ -29,7 +28,6 @@ import ( "os/exec" "os/signal" "path/filepath" - "regexp" "strings" "sync" "syscall" @@ -150,10 +148,6 @@ func (n *ExecNode) Client() (*rpc.Client, error) { return n.client, nil } -// wsAddrPattern is a regex used to read the WebSocket address from the node's -// log -var wsAddrPattern = regexp.MustCompile(`ws://[\d.:]+`) - // Start exec's the node passing the ID and service as command line arguments // and the node config encoded as JSON in the _P2P_NODE_CONFIG environment // variable @@ -196,23 +190,9 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) { n.Cmd = cmd // read the WebSocket address from the stderr logs - var wsAddr string - wsAddrC := make(chan string) - go func() { - s := bufio.NewScanner(stderrR) - for s.Scan() { - if strings.Contains(s.Text(), "WebSocket endpoint opened:") { - wsAddrC <- wsAddrPattern.FindString(s.Text()) - } - } - }() - select { - case wsAddr = <-wsAddrC: - if wsAddr == "" { - return errors.New("failed to read WebSocket address from stderr") - } - case <-time.After(10 * time.Second): - return errors.New("timed out waiting for WebSocket address on stderr") + wsAddr, err := findWSAddr(stderrR, 10*time.Second) + if err != nil { + return fmt.Errorf("error getting WebSocket address: %s", err) } // create the RPC client and load the node info diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 48d7c17301..6d90b4a9fc 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -154,30 +154,30 @@ type SimNode struct { } // Addr returns the node's discovery address -func (self *SimNode) Addr() []byte { - return []byte(self.Node().String()) +func (sn *SimNode) Addr() []byte { + return []byte(sn.Node().String()) } // Node returns a discover.Node representing the SimNode -func (self *SimNode) Node() *discover.Node { - return discover.NewNode(self.ID, net.IP{127, 0, 0, 1}, 30303, 30303) +func (sn *SimNode) Node() *discover.Node { + return discover.NewNode(sn.ID, net.IP{127, 0, 0, 1}, 30303, 30303) } // Client returns an rpc.Client which can be used to communicate with the // underlying services (it is set once the node has started) -func (self *SimNode) Client() (*rpc.Client, error) { - self.lock.RLock() - defer self.lock.RUnlock() - if self.client == nil { +func (sn *SimNode) Client() (*rpc.Client, error) { + sn.lock.RLock() + defer sn.lock.RUnlock() + if sn.client == nil { return nil, errors.New("node not started") } - return self.client, nil + return sn.client, nil } // ServeRPC serves RPC requests over the given connection by creating an // in-memory client to the node's RPC server -func (self *SimNode) ServeRPC(conn net.Conn) error { - handler, err := self.node.RPCHandler() +func (sn *SimNode) ServeRPC(conn net.Conn) error { + handler, err := sn.node.RPCHandler() if err != nil { return err } @@ -187,13 +187,13 @@ func (self *SimNode) ServeRPC(conn net.Conn) error { // Snapshots creates snapshots of the services by calling the // simulation_snapshot RPC method -func (self *SimNode) Snapshots() (map[string][]byte, error) { - self.lock.RLock() - services := make(map[string]node.Service, len(self.running)) - for name, service := range self.running { +func (sn *SimNode) Snapshots() (map[string][]byte, error) { + sn.lock.RLock() + services := make(map[string]node.Service, len(sn.running)) + for name, service := range sn.running { services[name] = service } - self.lock.RUnlock() + sn.lock.RUnlock() if len(services) == 0 { return nil, errors.New("no running services") } @@ -213,23 +213,23 @@ func (self *SimNode) Snapshots() (map[string][]byte, error) { } // Start registers the services and starts the underlying devp2p node -func (self *SimNode) Start(snapshots map[string][]byte) error { +func (sn *SimNode) Start(snapshots map[string][]byte) error { newService := func(name string) func(ctx *node.ServiceContext) (node.Service, error) { return func(nodeCtx *node.ServiceContext) (node.Service, error) { ctx := &ServiceContext{ - RPCDialer: self.adapter, + RPCDialer: sn.adapter, NodeContext: nodeCtx, - Config: self.config, + Config: sn.config, } if snapshots != nil { ctx.Snapshot = snapshots[name] } - serviceFunc := self.adapter.services[name] + serviceFunc := sn.adapter.services[name] service, err := serviceFunc(ctx) if err != nil { return nil, err } - self.running[name] = service + sn.running[name] = service return service, nil } } @@ -237,9 +237,9 @@ func (self *SimNode) Start(snapshots map[string][]byte) error { // ensure we only register the services once in the case of the node // being stopped and then started again var regErr error - self.registerOnce.Do(func() { - for _, name := range self.config.Services { - if err := self.node.Register(newService(name)); err != nil { + sn.registerOnce.Do(func() { + for _, name := range sn.config.Services { + if err := sn.node.Register(newService(name)); err != nil { regErr = err return } @@ -249,54 +249,54 @@ func (self *SimNode) Start(snapshots map[string][]byte) error { return regErr } - if err := self.node.Start(); err != nil { + if err := sn.node.Start(); err != nil { return err } // create an in-process RPC client - handler, err := self.node.RPCHandler() + handler, err := sn.node.RPCHandler() if err != nil { return err } - self.lock.Lock() - self.client = rpc.DialInProc(handler) - self.lock.Unlock() + sn.lock.Lock() + sn.client = rpc.DialInProc(handler) + sn.lock.Unlock() return nil } // Stop closes the RPC client and stops the underlying devp2p node -func (self *SimNode) Stop() error { - self.lock.Lock() - if self.client != nil { - self.client.Close() - self.client = nil +func (sn *SimNode) Stop() error { + sn.lock.Lock() + if sn.client != nil { + sn.client.Close() + sn.client = nil } - self.lock.Unlock() - return self.node.Stop() + sn.lock.Unlock() + return sn.node.Stop() } // Services returns a copy of the underlying services -func (self *SimNode) Services() []node.Service { - self.lock.RLock() - defer self.lock.RUnlock() - services := make([]node.Service, 0, len(self.running)) - for _, service := range self.running { +func (sn *SimNode) Services() []node.Service { + sn.lock.RLock() + defer sn.lock.RUnlock() + services := make([]node.Service, 0, len(sn.running)) + for _, service := range sn.running { services = append(services, service) } return services } // Server returns the underlying p2p.Server -func (self *SimNode) Server() *p2p.Server { - return self.node.Server() +func (sn *SimNode) Server() *p2p.Server { + return sn.node.Server() } // SubscribeEvents subscribes the given channel to peer events from the // underlying p2p.Server -func (self *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { - srv := self.Server() +func (sn *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription { + srv := sn.Server() if srv == nil { panic("node not running") } @@ -304,12 +304,12 @@ func (self *SimNode) SubscribeEvents(ch chan *p2p.PeerEvent) event.Subscription } // NodeInfo returns information about the node -func (self *SimNode) NodeInfo() *p2p.NodeInfo { - server := self.Server() +func (sn *SimNode) NodeInfo() *p2p.NodeInfo { + server := sn.Server() if server == nil { return &p2p.NodeInfo{ - ID: self.ID.String(), - Enode: self.Node().String(), + ID: sn.ID.String(), + Enode: sn.Node().String(), } } return server.NodeInfo() diff --git a/p2p/simulations/adapters/state.go b/p2p/simulations/adapters/state.go index 0d4ecfb0ff..78dfb11f95 100644 --- a/p2p/simulations/adapters/state.go +++ b/p2p/simulations/adapters/state.go @@ -20,12 +20,12 @@ type SimStateStore struct { m map[string][]byte } -func (self *SimStateStore) Load(s string) ([]byte, error) { - return self.m[s], nil +func (st *SimStateStore) Load(s string) ([]byte, error) { + return st.m[s], nil } -func (self *SimStateStore) Save(s string, data []byte) error { - self.m[s] = data +func (st *SimStateStore) Save(s string, data []byte) error { + st.m[s] = data return nil } diff --git a/p2p/simulations/adapters/ws.go b/p2p/simulations/adapters/ws.go new file mode 100644 index 0000000000..979a21709e --- /dev/null +++ b/p2p/simulations/adapters/ws.go @@ -0,0 +1,51 @@ +package adapters + +import ( + "bufio" + "errors" + "io" + "regexp" + "strings" + "time" +) + +// wsAddrPattern is a regex used to read the WebSocket address from the node's +// log +var wsAddrPattern = regexp.MustCompile(`ws://[\d.:]+`) + +func matchWSAddr(str string) (string, bool) { + if !strings.Contains(str, "WebSocket endpoint opened") { + return "", false + } + + return wsAddrPattern.FindString(str), true +} + +// findWSAddr scans through reader r, looking for the log entry with +// WebSocket address information. +func findWSAddr(r io.Reader, timeout time.Duration) (string, error) { + ch := make(chan string) + + go func() { + s := bufio.NewScanner(r) + for s.Scan() { + addr, ok := matchWSAddr(s.Text()) + if ok { + ch <- addr + } + } + close(ch) + }() + + var wsAddr string + select { + case wsAddr = <-ch: + if wsAddr == "" { + return "", errors.New("empty result") + } + case <-time.After(timeout): + return "", errors.New("timed out") + } + + return wsAddr, nil +} diff --git a/p2p/simulations/adapters/ws_test.go b/p2p/simulations/adapters/ws_test.go new file mode 100644 index 0000000000..0bb9ed2b2b --- /dev/null +++ b/p2p/simulations/adapters/ws_test.go @@ -0,0 +1,21 @@ +package adapters + +import ( + "bytes" + "testing" + "time" +) + +func TestFindWSAddr(t *testing.T) { + line := `t=2018-05-02T19:00:45+0200 lvl=info msg="WebSocket endpoint opened" node.id=26c65a606d1125a44695bc08573190d047152b6b9a776ccbbe593e90f91444d9c1ebdadac6a775ad9fdd0923468a1d698ed3a842c1fb89c1bc0f9d4801f8c39c url=ws://127.0.0.1:59975` + buf := bytes.NewBufferString(line) + got, err := findWSAddr(buf, 10*time.Second) + if err != nil { + t.Fatalf("Failed to find addr: %v", err) + } + expected := `ws://127.0.0.1:59975` + + if got != expected { + t.Fatalf("Expected to get '%s', but got '%s'", expected, got) + } +} diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index caf428ece1..1a2c1e8ff4 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -74,22 +74,22 @@ func NewNetwork(nodeAdapter adapters.NodeAdapter, conf *NetworkConfig) *Network } // Events returns the output event feed of the Network. -func (self *Network) Events() *event.Feed { - return &self.events +func (net *Network) Events() *event.Feed { + return &net.events } // NewNode adds a new node to the network with a random ID -func (self *Network) NewNode() (*Node, error) { +func (net *Network) NewNode() (*Node, error) { conf := adapters.RandomNodeConfig() - conf.Services = []string{self.DefaultService} - return self.NewNodeWithConfig(conf) + conf.Services = []string{net.DefaultService} + return net.NewNodeWithConfig(conf) } // NewNodeWithConfig adds a new node to the network with the given config, // returning an error if a node with the same ID or name already exists -func (self *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) { - self.lock.Lock() - defer self.lock.Unlock() +func (net *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) { + net.lock.Lock() + defer net.lock.Unlock() // create a random ID and PrivateKey if not set if conf.ID == (discover.NodeID{}) { @@ -100,31 +100,31 @@ func (self *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) id := conf.ID if conf.Reachable == nil { conf.Reachable = func(otherID discover.NodeID) bool { - _, err := self.InitConn(conf.ID, otherID) + _, err := net.InitConn(conf.ID, otherID) return err == nil } } // assign a name to the node if not set if conf.Name == "" { - conf.Name = fmt.Sprintf("node%02d", len(self.Nodes)+1) + conf.Name = fmt.Sprintf("node%02d", len(net.Nodes)+1) } // check the node doesn't already exist - if node := self.getNode(id); node != nil { + if node := net.getNode(id); node != nil { return nil, fmt.Errorf("node with ID %q already exists", id) } - if node := self.getNodeByName(conf.Name); node != nil { + if node := net.getNodeByName(conf.Name); node != nil { return nil, fmt.Errorf("node with name %q already exists", conf.Name) } // if no services are configured, use the default service if len(conf.Services) == 0 { - conf.Services = []string{self.DefaultService} + conf.Services = []string{net.DefaultService} } // use the NodeAdapter to create the node - adapterNode, err := self.nodeAdapter.NewNode(conf) + adapterNode, err := net.nodeAdapter.NewNode(conf) if err != nil { return nil, err } @@ -133,27 +133,27 @@ func (self *Network) NewNodeWithConfig(conf *adapters.NodeConfig) (*Node, error) Config: conf, } log.Trace(fmt.Sprintf("node %v created", id)) - self.nodeMap[id] = len(self.Nodes) - self.Nodes = append(self.Nodes, node) + net.nodeMap[id] = len(net.Nodes) + net.Nodes = append(net.Nodes, node) // emit a "control" event - self.events.Send(ControlEvent(node)) + net.events.Send(ControlEvent(node)) return node, nil } // Config returns the network configuration -func (self *Network) Config() *NetworkConfig { - return &self.NetworkConfig +func (net *Network) Config() *NetworkConfig { + return &net.NetworkConfig } // StartAll starts all nodes in the network -func (self *Network) StartAll() error { - for _, node := range self.Nodes { +func (net *Network) StartAll() error { + for _, node := range net.Nodes { if node.Up { continue } - if err := self.Start(node.ID()); err != nil { + if err := net.Start(node.ID()); err != nil { return err } } @@ -161,12 +161,12 @@ func (self *Network) StartAll() error { } // StopAll stops all nodes in the network -func (self *Network) StopAll() error { - for _, node := range self.Nodes { +func (net *Network) StopAll() error { + for _, node := range net.Nodes { if !node.Up { continue } - if err := self.Stop(node.ID()); err != nil { + if err := net.Stop(node.ID()); err != nil { return err } } @@ -174,21 +174,21 @@ func (self *Network) StopAll() error { } // Start starts the node with the given ID -func (self *Network) Start(id discover.NodeID) error { - return self.startWithSnapshots(id, nil) +func (net *Network) Start(id discover.NodeID) error { + return net.startWithSnapshots(id, nil) } // startWithSnapshots starts the node with the given ID using the give // snapshots -func (self *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error { - node := self.GetNode(id) +func (net *Network) startWithSnapshots(id discover.NodeID, snapshots map[string][]byte) error { + node := net.GetNode(id) if node == nil { return fmt.Errorf("node %v does not exist", id) } if node.Up { return fmt.Errorf("node %v already up", id) } - log.Trace(fmt.Sprintf("starting node %v: %v using %v", id, node.Up, self.nodeAdapter.Name())) + log.Trace(fmt.Sprintf("starting node %v: %v using %v", id, node.Up, net.nodeAdapter.Name())) if err := node.Start(snapshots); err != nil { log.Warn(fmt.Sprintf("start up failed: %v", err)) return err @@ -196,7 +196,7 @@ func (self *Network) startWithSnapshots(id discover.NodeID, snapshots map[string node.Up = true log.Info(fmt.Sprintf("started node %v: %v", id, node.Up)) - self.events.Send(NewEvent(node)) + net.events.Send(NewEvent(node)) // subscribe to peer events client, err := node.Client() @@ -208,22 +208,22 @@ func (self *Network) startWithSnapshots(id discover.NodeID, snapshots map[string if err != nil { return fmt.Errorf("error getting peer events for node %v: %s", id, err) } - go self.watchPeerEvents(id, events, sub) + go net.watchPeerEvents(id, events, sub) return nil } // watchPeerEvents reads peer events from the given channel and emits // corresponding network events -func (self *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEvent, sub event.Subscription) { +func (net *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEvent, sub event.Subscription) { defer func() { sub.Unsubscribe() // assume the node is now down - self.lock.Lock() - node := self.getNode(id) + net.lock.Lock() + node := net.getNode(id) node.Up = false - self.lock.Unlock() - self.events.Send(NewEvent(node)) + net.lock.Unlock() + net.events.Send(NewEvent(node)) }() for { select { @@ -235,16 +235,16 @@ func (self *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEv switch event.Type { case p2p.PeerEventTypeAdd: - self.DidConnect(id, peer) + net.DidConnect(id, peer) case p2p.PeerEventTypeDrop: - self.DidDisconnect(id, peer) + net.DidDisconnect(id, peer) case p2p.PeerEventTypeMsgSend: - self.DidSend(id, peer, event.Protocol, *event.MsgCode) + net.DidSend(id, peer, event.Protocol, *event.MsgCode) case p2p.PeerEventTypeMsgRecv: - self.DidReceive(peer, id, event.Protocol, *event.MsgCode) + net.DidReceive(peer, id, event.Protocol, *event.MsgCode) } @@ -258,8 +258,8 @@ func (self *Network) watchPeerEvents(id discover.NodeID, events chan *p2p.PeerEv } // Stop stops the node with the given ID -func (self *Network) Stop(id discover.NodeID) error { - node := self.GetNode(id) +func (net *Network) Stop(id discover.NodeID) error { + node := net.GetNode(id) if node == nil { return fmt.Errorf("node %v does not exist", id) } @@ -272,15 +272,15 @@ func (self *Network) Stop(id discover.NodeID) error { node.Up = false log.Info(fmt.Sprintf("stop node %v: %v", id, node.Up)) - self.events.Send(ControlEvent(node)) + net.events.Send(ControlEvent(node)) return nil } // Connect connects two nodes together by calling the "admin_addPeer" RPC // method on the "one" node so that it connects to the "other" node -func (self *Network) Connect(oneID, otherID discover.NodeID) error { +func (net *Network) Connect(oneID, otherID discover.NodeID) error { log.Debug(fmt.Sprintf("connecting %s to %s", oneID, otherID)) - conn, err := self.InitConn(oneID, otherID) + conn, err := net.InitConn(oneID, otherID) if err != nil { return err } @@ -288,14 +288,14 @@ func (self *Network) Connect(oneID, otherID discover.NodeID) error { if err != nil { return err } - self.events.Send(ControlEvent(conn)) + net.events.Send(ControlEvent(conn)) return client.Call(nil, "admin_addPeer", string(conn.other.Addr())) } // Disconnect disconnects two nodes by calling the "admin_removePeer" RPC // method on the "one" node so that it disconnects from the "other" node -func (self *Network) Disconnect(oneID, otherID discover.NodeID) error { - conn := self.GetConn(oneID, otherID) +func (net *Network) Disconnect(oneID, otherID discover.NodeID) error { + conn := net.GetConn(oneID, otherID) if conn == nil { return fmt.Errorf("connection between %v and %v does not exist", oneID, otherID) } @@ -306,13 +306,13 @@ func (self *Network) Disconnect(oneID, otherID discover.NodeID) error { if err != nil { return err } - self.events.Send(ControlEvent(conn)) + net.events.Send(ControlEvent(conn)) return client.Call(nil, "admin_removePeer", string(conn.other.Addr())) } // DidConnect tracks the fact that the "one" node connected to the "other" node -func (self *Network) DidConnect(one, other discover.NodeID) error { - conn, err := self.GetOrCreateConn(one, other) +func (net *Network) DidConnect(one, other discover.NodeID) error { + conn, err := net.GetOrCreateConn(one, other) if err != nil { return fmt.Errorf("connection between %v and %v does not exist", one, other) } @@ -320,14 +320,14 @@ func (self *Network) DidConnect(one, other discover.NodeID) error { return fmt.Errorf("%v and %v already connected", one, other) } conn.Up = true - self.events.Send(NewEvent(conn)) + net.events.Send(NewEvent(conn)) return nil } // DidDisconnect tracks the fact that the "one" node disconnected from the // "other" node -func (self *Network) DidDisconnect(one, other discover.NodeID) error { - conn := self.GetConn(one, other) +func (net *Network) DidDisconnect(one, other discover.NodeID) error { + conn := net.GetConn(one, other) if conn == nil { return fmt.Errorf("connection between %v and %v does not exist", one, other) } @@ -336,12 +336,12 @@ func (self *Network) DidDisconnect(one, other discover.NodeID) error { } conn.Up = false conn.initiated = time.Now().Add(-dialBanTimeout) - self.events.Send(NewEvent(conn)) + net.events.Send(NewEvent(conn)) return nil } // DidSend tracks the fact that "sender" sent a message to "receiver" -func (self *Network) DidSend(sender, receiver discover.NodeID, proto string, code uint64) error { +func (net *Network) DidSend(sender, receiver discover.NodeID, proto string, code uint64) error { msg := &Msg{ One: sender, Other: receiver, @@ -349,12 +349,12 @@ func (self *Network) DidSend(sender, receiver discover.NodeID, proto string, cod Code: code, Received: false, } - self.events.Send(NewEvent(msg)) + net.events.Send(NewEvent(msg)) return nil } // DidReceive tracks the fact that "receiver" received a message from "sender" -func (self *Network) DidReceive(sender, receiver discover.NodeID, proto string, code uint64) error { +func (net *Network) DidReceive(sender, receiver discover.NodeID, proto string, code uint64) error { msg := &Msg{ One: sender, Other: receiver, @@ -362,36 +362,36 @@ func (self *Network) DidReceive(sender, receiver discover.NodeID, proto string, Code: code, Received: true, } - self.events.Send(NewEvent(msg)) + net.events.Send(NewEvent(msg)) return nil } // GetNode gets the node with the given ID, returning nil if the node does not // exist -func (self *Network) GetNode(id discover.NodeID) *Node { - self.lock.Lock() - defer self.lock.Unlock() - return self.getNode(id) +func (net *Network) GetNode(id discover.NodeID) *Node { + net.lock.Lock() + defer net.lock.Unlock() + return net.getNode(id) } // GetNode gets the node with the given name, returning nil if the node does // not exist -func (self *Network) GetNodeByName(name string) *Node { - self.lock.Lock() - defer self.lock.Unlock() - return self.getNodeByName(name) +func (net *Network) GetNodeByName(name string) *Node { + net.lock.Lock() + defer net.lock.Unlock() + return net.getNodeByName(name) } -func (self *Network) getNode(id discover.NodeID) *Node { - i, found := self.nodeMap[id] +func (net *Network) getNode(id discover.NodeID) *Node { + i, found := net.nodeMap[id] if !found { return nil } - return self.Nodes[i] + return net.Nodes[i] } -func (self *Network) getNodeByName(name string) *Node { - for _, node := range self.Nodes { +func (net *Network) getNodeByName(name string) *Node { + for _, node := range net.Nodes { if node.Config.Name == name { return node } @@ -400,40 +400,40 @@ func (self *Network) getNodeByName(name string) *Node { } // GetNodes returns the existing nodes -func (self *Network) GetNodes() (nodes []*Node) { - self.lock.Lock() - defer self.lock.Unlock() +func (net *Network) GetNodes() (nodes []*Node) { + net.lock.Lock() + defer net.lock.Unlock() - nodes = append(nodes, self.Nodes...) + nodes = append(nodes, net.Nodes...) return nodes } // GetConn returns the connection which exists between "one" and "other" // regardless of which node initiated the connection -func (self *Network) GetConn(oneID, otherID discover.NodeID) *Conn { - self.lock.Lock() - defer self.lock.Unlock() - return self.getConn(oneID, otherID) +func (net *Network) GetConn(oneID, otherID discover.NodeID) *Conn { + net.lock.Lock() + defer net.lock.Unlock() + return net.getConn(oneID, otherID) } // GetOrCreateConn is like GetConn but creates the connection if it doesn't // already exist -func (self *Network) GetOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) { - self.lock.Lock() - defer self.lock.Unlock() - return self.getOrCreateConn(oneID, otherID) +func (net *Network) GetOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) { + net.lock.Lock() + defer net.lock.Unlock() + return net.getOrCreateConn(oneID, otherID) } -func (self *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) { - if conn := self.getConn(oneID, otherID); conn != nil { +func (net *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, error) { + if conn := net.getConn(oneID, otherID); conn != nil { return conn, nil } - one := self.getNode(oneID) + one := net.getNode(oneID) if one == nil { return nil, fmt.Errorf("node %v does not exist", oneID) } - other := self.getNode(otherID) + other := net.getNode(otherID) if other == nil { return nil, fmt.Errorf("node %v does not exist", otherID) } @@ -444,18 +444,18 @@ func (self *Network) getOrCreateConn(oneID, otherID discover.NodeID) (*Conn, err other: other, } label := ConnLabel(oneID, otherID) - self.connMap[label] = len(self.Conns) - self.Conns = append(self.Conns, conn) + net.connMap[label] = len(net.Conns) + net.Conns = append(net.Conns, conn) return conn, nil } -func (self *Network) getConn(oneID, otherID discover.NodeID) *Conn { +func (net *Network) getConn(oneID, otherID discover.NodeID) *Conn { label := ConnLabel(oneID, otherID) - i, found := self.connMap[label] + i, found := net.connMap[label] if !found { return nil } - return self.Conns[i] + return net.Conns[i] } // InitConn(one, other) retrieves the connectiton model for the connection between @@ -466,13 +466,13 @@ func (self *Network) getConn(oneID, otherID discover.NodeID) *Conn { // it also checks whether there has been recent attempt to connect the peers // this is cheating as the simulation is used as an oracle and know about // remote peers attempt to connect to a node which will then not initiate the connection -func (self *Network) InitConn(oneID, otherID discover.NodeID) (*Conn, error) { - self.lock.Lock() - defer self.lock.Unlock() +func (net *Network) InitConn(oneID, otherID discover.NodeID) (*Conn, error) { + net.lock.Lock() + defer net.lock.Unlock() if oneID == otherID { return nil, fmt.Errorf("refusing to connect to self %v", oneID) } - conn, err := self.getOrCreateConn(oneID, otherID) + conn, err := net.getOrCreateConn(oneID, otherID) if err != nil { return nil, err } @@ -491,28 +491,28 @@ func (self *Network) InitConn(oneID, otherID discover.NodeID) (*Conn, error) { } // Shutdown stops all nodes in the network and closes the quit channel -func (self *Network) Shutdown() { - for _, node := range self.Nodes { +func (net *Network) Shutdown() { + for _, node := range net.Nodes { log.Debug(fmt.Sprintf("stopping node %s", node.ID().TerminalString())) if err := node.Stop(); err != nil { log.Warn(fmt.Sprintf("error stopping node %s", node.ID().TerminalString()), "err", err) } } - close(self.quitc) + close(net.quitc) } //Reset resets all network properties: //emtpies the nodes and the connection list -func (self *Network) Reset() { - self.lock.Lock() - defer self.lock.Unlock() +func (net *Network) Reset() { + net.lock.Lock() + defer net.lock.Unlock() //re-initialize the maps - self.connMap = make(map[string]int) - self.nodeMap = make(map[discover.NodeID]int) + net.connMap = make(map[string]int) + net.nodeMap = make(map[discover.NodeID]int) - self.Nodes = nil - self.Conns = nil + net.Nodes = nil + net.Conns = nil } // Node is a wrapper around adapters.Node which is used to track the status @@ -528,37 +528,37 @@ type Node struct { } // ID returns the ID of the node -func (self *Node) ID() discover.NodeID { - return self.Config.ID +func (n *Node) ID() discover.NodeID { + return n.Config.ID } // String returns a log-friendly string -func (self *Node) String() string { - return fmt.Sprintf("Node %v", self.ID().TerminalString()) +func (n *Node) String() string { + return fmt.Sprintf("Node %v", n.ID().TerminalString()) } // NodeInfo returns information about the node -func (self *Node) NodeInfo() *p2p.NodeInfo { +func (n *Node) NodeInfo() *p2p.NodeInfo { // avoid a panic if the node is not started yet - if self.Node == nil { + if n.Node == nil { return nil } - info := self.Node.NodeInfo() - info.Name = self.Config.Name + info := n.Node.NodeInfo() + info.Name = n.Config.Name return info } // MarshalJSON implements the json.Marshaler interface so that the encoded // JSON includes the NodeInfo -func (self *Node) MarshalJSON() ([]byte, error) { +func (n *Node) MarshalJSON() ([]byte, error) { return json.Marshal(struct { Info *p2p.NodeInfo `json:"info,omitempty"` Config *adapters.NodeConfig `json:"config,omitempty"` Up bool `json:"up"` }{ - Info: self.NodeInfo(), - Config: self.Config, - Up: self.Up, + Info: n.NodeInfo(), + Config: n.Config, + Up: n.Up, }) } @@ -580,19 +580,19 @@ type Conn struct { } // nodesUp returns whether both nodes are currently up -func (self *Conn) nodesUp() error { - if !self.one.Up { - return fmt.Errorf("one %v is not up", self.One) +func (c *Conn) nodesUp() error { + if !c.one.Up { + return fmt.Errorf("one %v is not up", c.One) } - if !self.other.Up { - return fmt.Errorf("other %v is not up", self.Other) + if !c.other.Up { + return fmt.Errorf("other %v is not up", c.Other) } return nil } // String returns a log-friendly string -func (self *Conn) String() string { - return fmt.Sprintf("Conn %v->%v", self.One.TerminalString(), self.Other.TerminalString()) +func (c *Conn) String() string { + return fmt.Sprintf("Conn %v->%v", c.One.TerminalString(), c.Other.TerminalString()) } // Msg represents a p2p message sent between two nodes in the network @@ -605,8 +605,8 @@ type Msg struct { } // String returns a log-friendly string -func (self *Msg) String() string { - return fmt.Sprintf("Msg(%d) %v->%v", self.Code, self.One.TerminalString(), self.Other.TerminalString()) +func (m *Msg) String() string { + return fmt.Sprintf("Msg(%d) %v->%v", m.Code, m.One.TerminalString(), m.Other.TerminalString()) } // ConnLabel generates a deterministic string which represents a connection @@ -640,14 +640,14 @@ type NodeSnapshot struct { } // Snapshot creates a network snapshot -func (self *Network) Snapshot() (*Snapshot, error) { - self.lock.Lock() - defer self.lock.Unlock() +func (net *Network) Snapshot() (*Snapshot, error) { + net.lock.Lock() + defer net.lock.Unlock() snap := &Snapshot{ - Nodes: make([]NodeSnapshot, len(self.Nodes)), - Conns: make([]Conn, len(self.Conns)), + Nodes: make([]NodeSnapshot, len(net.Nodes)), + Conns: make([]Conn, len(net.Conns)), } - for i, node := range self.Nodes { + for i, node := range net.Nodes { snap.Nodes[i] = NodeSnapshot{Node: *node} if !node.Up { continue @@ -658,33 +658,33 @@ func (self *Network) Snapshot() (*Snapshot, error) { } snap.Nodes[i].Snapshots = snapshots } - for i, conn := range self.Conns { + for i, conn := range net.Conns { snap.Conns[i] = *conn } return snap, nil } // Load loads a network snapshot -func (self *Network) Load(snap *Snapshot) error { +func (net *Network) Load(snap *Snapshot) error { for _, n := range snap.Nodes { - if _, err := self.NewNodeWithConfig(n.Node.Config); err != nil { + if _, err := net.NewNodeWithConfig(n.Node.Config); err != nil { return err } if !n.Node.Up { continue } - if err := self.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil { + if err := net.startWithSnapshots(n.Node.Config.ID, n.Snapshots); err != nil { return err } } for _, conn := range snap.Conns { - if !self.GetNode(conn.One).Up || !self.GetNode(conn.Other).Up { + if !net.GetNode(conn.One).Up || !net.GetNode(conn.Other).Up { //in this case, at least one of the nodes of a connection is not up, //so it would result in the snapshot `Load` to fail continue } - if err := self.Connect(conn.One, conn.Other); err != nil { + if err := net.Connect(conn.One, conn.Other); err != nil { return err } } @@ -692,7 +692,7 @@ func (self *Network) Load(snap *Snapshot) error { } // Subscribe reads control events from a channel and executes them -func (self *Network) Subscribe(events chan *Event) { +func (net *Network) Subscribe(events chan *Event) { for { select { case event, ok := <-events: @@ -700,23 +700,23 @@ func (self *Network) Subscribe(events chan *Event) { return } if event.Control { - self.executeControlEvent(event) + net.executeControlEvent(event) } - case <-self.quitc: + case <-net.quitc: return } } } -func (self *Network) executeControlEvent(event *Event) { +func (net *Network) executeControlEvent(event *Event) { log.Trace("execute control event", "type", event.Type, "event", event) switch event.Type { case EventTypeNode: - if err := self.executeNodeEvent(event); err != nil { + if err := net.executeNodeEvent(event); err != nil { log.Error("error executing node event", "event", event, "err", err) } case EventTypeConn: - if err := self.executeConnEvent(event); err != nil { + if err := net.executeConnEvent(event); err != nil { log.Error("error executing conn event", "event", event, "err", err) } case EventTypeMsg: @@ -724,21 +724,21 @@ func (self *Network) executeControlEvent(event *Event) { } } -func (self *Network) executeNodeEvent(e *Event) error { +func (net *Network) executeNodeEvent(e *Event) error { if !e.Node.Up { - return self.Stop(e.Node.ID()) + return net.Stop(e.Node.ID()) } - if _, err := self.NewNodeWithConfig(e.Node.Config); err != nil { + if _, err := net.NewNodeWithConfig(e.Node.Config); err != nil { return err } - return self.Start(e.Node.ID()) + return net.Start(e.Node.ID()) } -func (self *Network) executeConnEvent(e *Event) error { +func (net *Network) executeConnEvent(e *Event) error { if e.Conn.Up { - return self.Connect(e.Conn.One, e.Conn.Other) + return net.Connect(e.Conn.One, e.Conn.Other) } else { - return self.Disconnect(e.Conn.One, e.Conn.Other) + return net.Disconnect(e.Conn.One, e.Conn.Other) } } diff --git a/p2p/testing/peerpool.go b/p2p/testing/peerpool.go index 45c6e61425..ed00396e23 100644 --- a/p2p/testing/peerpool.go +++ b/p2p/testing/peerpool.go @@ -39,29 +39,29 @@ func NewTestPeerPool() *TestPeerPool { return &TestPeerPool{peers: make(map[discover.NodeID]TestPeer)} } -func (self *TestPeerPool) Add(p TestPeer) { - self.lock.Lock() - defer self.lock.Unlock() - log.Trace(fmt.Sprintf("pp add peer %v", p.ID())) - self.peers[p.ID()] = p +func (p *TestPeerPool) Add(peer TestPeer) { + p.lock.Lock() + defer p.lock.Unlock() + log.Trace(fmt.Sprintf("pp add peer %v", peer.ID())) + p.peers[peer.ID()] = peer } -func (self *TestPeerPool) Remove(p TestPeer) { - self.lock.Lock() - defer self.lock.Unlock() - delete(self.peers, p.ID()) +func (p *TestPeerPool) Remove(peer TestPeer) { + p.lock.Lock() + defer p.lock.Unlock() + delete(p.peers, peer.ID()) } -func (self *TestPeerPool) Has(id discover.NodeID) bool { - self.lock.Lock() - defer self.lock.Unlock() - _, ok := self.peers[id] +func (p *TestPeerPool) Has(id discover.NodeID) bool { + p.lock.Lock() + defer p.lock.Unlock() + _, ok := p.peers[id] return ok } -func (self *TestPeerPool) Get(id discover.NodeID) TestPeer { - self.lock.Lock() - defer self.lock.Unlock() - return self.peers[id] +func (p *TestPeerPool) Get(id discover.NodeID) TestPeer { + p.lock.Lock() + defer p.lock.Unlock() + return p.peers[id] } diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go index 361285f06e..8f73bfa03e 100644 --- a/p2p/testing/protocolsession.go +++ b/p2p/testing/protocolsession.go @@ -78,10 +78,10 @@ type Disconnect struct { } // trigger sends messages from peers -func (self *ProtocolSession) trigger(trig Trigger) error { - simNode, ok := self.adapter.GetNode(trig.Peer) +func (s *ProtocolSession) trigger(trig Trigger) error { + simNode, ok := s.adapter.GetNode(trig.Peer) if !ok { - return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(self.IDs)) + return fmt.Errorf("trigger: peer %v does not exist (1- %v)", trig.Peer, len(s.IDs)) } mockNode, ok := simNode.Services()[0].(*mockNode) if !ok { @@ -107,7 +107,7 @@ func (self *ProtocolSession) trigger(trig Trigger) error { } // expect checks an expectation of a message sent out by the pivot node -func (self *ProtocolSession) expect(exps []Expect) error { +func (s *ProtocolSession) expect(exps []Expect) error { // construct a map of expectations for each node peerExpects := make(map[discover.NodeID][]Expect) for _, exp := range exps { @@ -120,9 +120,9 @@ func (self *ProtocolSession) expect(exps []Expect) error { // construct a map of mockNodes for each node mockNodes := make(map[discover.NodeID]*mockNode) for nodeID := range peerExpects { - simNode, ok := self.adapter.GetNode(nodeID) + simNode, ok := s.adapter.GetNode(nodeID) if !ok { - return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(self.IDs)) + return fmt.Errorf("trigger: peer %v does not exist (1- %v)", nodeID, len(s.IDs)) } mockNode, ok := simNode.Services()[0].(*mockNode) if !ok { @@ -202,9 +202,9 @@ func (self *ProtocolSession) expect(exps []Expect) error { } // TestExchanges tests a series of exchanges against the session -func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error { +func (s *ProtocolSession) TestExchanges(exchanges ...Exchange) error { for i, e := range exchanges { - if err := self.testExchange(e); err != nil { + if err := s.testExchange(e); err != nil { return fmt.Errorf("exchange #%d %q: %v", i, e.Label, err) } log.Trace(fmt.Sprintf("exchange #%d %q: run successfully", i, e.Label)) @@ -214,14 +214,14 @@ func (self *ProtocolSession) TestExchanges(exchanges ...Exchange) error { // testExchange tests a single Exchange. // Default timeout value is 2 seconds. -func (self *ProtocolSession) testExchange(e Exchange) error { +func (s *ProtocolSession) testExchange(e Exchange) error { errc := make(chan error) done := make(chan struct{}) defer close(done) go func() { for _, trig := range e.Triggers { - err := self.trigger(trig) + err := s.trigger(trig) if err != nil { errc <- err return @@ -229,7 +229,7 @@ func (self *ProtocolSession) testExchange(e Exchange) error { } select { - case errc <- self.expect(e.Expects): + case errc <- s.expect(e.Expects): case <-done: } }() @@ -250,7 +250,7 @@ func (self *ProtocolSession) testExchange(e Exchange) error { // TestDisconnected tests the disconnections given as arguments // the disconnect structs describe what disconnect error is expected on which peer -func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { +func (s *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error { expects := make(map[discover.NodeID]error) for _, disconnect := range disconnects { expects[disconnect.Peer] = disconnect.Error @@ -259,7 +259,7 @@ func (self *ProtocolSession) TestDisconnected(disconnects ...*Disconnect) error timeout := time.After(time.Second) for len(expects) > 0 { select { - case event := <-self.events: + case event := <-s.events: if event.Type != p2p.PeerEventTypeDrop { continue } diff --git a/p2p/testing/protocoltester.go b/p2p/testing/protocoltester.go index a797412d60..636613c57a 100644 --- a/p2p/testing/protocoltester.go +++ b/p2p/testing/protocoltester.go @@ -101,24 +101,24 @@ func NewProtocolTester(t *testing.T, id discover.NodeID, n int, run func(*p2p.Pe } // Stop stops the p2p server -func (self *ProtocolTester) Stop() error { - self.Server.Stop() +func (t *ProtocolTester) Stop() error { + t.Server.Stop() return nil } // Connect brings up the remote peer node and connects it using the // p2p/simulations network connection with the in memory network adapter -func (self *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) { +func (t *ProtocolTester) Connect(selfID discover.NodeID, peers ...*adapters.NodeConfig) { for _, peer := range peers { log.Trace(fmt.Sprintf("start node %v", peer.ID)) - if _, err := self.network.NewNodeWithConfig(peer); err != nil { + if _, err := t.network.NewNodeWithConfig(peer); err != nil { panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err)) } - if err := self.network.Start(peer.ID); err != nil { + if err := t.network.Start(peer.ID); err != nil { panic(fmt.Sprintf("error starting peer %v: %v", peer.ID, err)) } log.Trace(fmt.Sprintf("connect to %v", peer.ID)) - if err := self.network.Connect(selfID, peer.ID); err != nil { + if err := t.network.Connect(selfID, peer.ID); err != nil { panic(fmt.Sprintf("error connecting to peer %v: %v", peer.ID, err)) } } diff --git a/params/version.go b/params/version.go index 181f84631e..1e8c43bf81 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release + VersionPatch = 9 // Patch version component of the current release VersionMeta = "unstable" // Version metadata to append to the version string ) diff --git a/rlp/decode.go b/rlp/decode.go index 60d9dab2b5..dbbe599597 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -29,6 +29,23 @@ import ( ) var ( + // EOL is returned when the end of the current list + // has been reached during streaming. + EOL = errors.New("rlp: end of list") + + // Actual Errors + ErrExpectedString = errors.New("rlp: expected String or Byte") + ErrExpectedList = errors.New("rlp: expected List") + ErrCanonInt = errors.New("rlp: non-canonical integer format") + ErrCanonSize = errors.New("rlp: non-canonical size information") + ErrElemTooLarge = errors.New("rlp: element is larger than containing list") + ErrValueTooLarge = errors.New("rlp: value size exceeds available input length") + ErrMoreThanOneValue = errors.New("rlp: input contains more than one value") + + // internal errors + errNotInList = errors.New("rlp: call of ListEnd outside of any list") + errNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL") + errUintOverflow = errors.New("rlp: uint overflow") errNoPointer = errors.New("rlp: interface given to Decode must be a pointer") errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil") ) @@ -274,9 +291,8 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) { if typ.Kind() == reflect.Array { return decodeByteArray, nil - } else { - return decodeByteSlice, nil } + return decodeByteSlice, nil } etypeinfo, err := cachedTypeInfo1(etype, tags{}) if err != nil { @@ -555,29 +571,6 @@ func (k Kind) String() string { } } -var ( - // EOL is returned when the end of the current list - // has been reached during streaming. - EOL = errors.New("rlp: end of list") - - // Actual Errors - ErrExpectedString = errors.New("rlp: expected String or Byte") - ErrExpectedList = errors.New("rlp: expected List") - ErrCanonInt = errors.New("rlp: non-canonical integer format") - ErrCanonSize = errors.New("rlp: non-canonical size information") - ErrElemTooLarge = errors.New("rlp: element is larger than containing list") - ErrValueTooLarge = errors.New("rlp: value size exceeds available input length") - - // This error is reported by DecodeBytes if the slice contains - // additional data after the first RLP value. - ErrMoreThanOneValue = errors.New("rlp: input contains more than one value") - - // internal errors - errNotInList = errors.New("rlp: call of ListEnd outside of any list") - errNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL") - errUintOverflow = errors.New("rlp: uint overflow") -) - // ByteReader must be implemented by any input reader for a Stream. It // is implemented by e.g. bufio.Reader and bytes.Reader. type ByteReader interface { diff --git a/rlp/encode.go b/rlp/encode.go index 44592c2f53..445b4b5b21 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -92,7 +92,7 @@ func Encode(w io.Writer, val interface{}) error { return eb.toWriter(w) } -// EncodeBytes returns the RLP encoding of val. +// EncodeToBytes returns the RLP encoding of val. // Please see the documentation of Encode for the encoding rules. func EncodeToBytes(val interface{}) ([]byte, error) { eb := encbufPool.Get().(*encbuf) @@ -104,7 +104,7 @@ func EncodeToBytes(val interface{}) ([]byte, error) { return eb.toBytes(), nil } -// EncodeReader returns a reader from which the RLP encoding of val +// EncodeToReader returns a reader from which the RLP encoding of val // can be read. The returned size is the total size of the encoded // data. // @@ -151,11 +151,10 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int { if size < 56 { buf[0] = smalltag + byte(size) return 1 - } else { - sizesize := putint(buf[1:], size) - buf[0] = largetag + byte(sizesize) - return sizesize + 1 } + sizesize := putint(buf[1:], size) + buf[0] = largetag + byte(sizesize) + return sizesize + 1 } // encbufs are pooled. @@ -218,7 +217,7 @@ func (w *encbuf) list() *listhead { func (w *encbuf) listEnd(lh *listhead) { lh.size = w.size() - lh.offset - lh.size if lh.size < 56 { - w.lhsize += 1 // length encoded into kind tag + w.lhsize++ // length encoded into kind tag } else { w.lhsize += 1 + intsize(uint64(lh.size)) } @@ -322,10 +321,9 @@ func (r *encReader) next() []byte { p := r.buf.str[r.strpos:head.offset] r.strpos += sizebefore return p - } else { - r.lhpos++ - return head.encode(r.buf.sizebuf) } + r.lhpos++ + return head.encode(r.buf.sizebuf) case r.strpos < len(r.buf.str): // String data at the end, after all list headers. @@ -576,9 +574,8 @@ func makePtrWriter(typ reflect.Type) (writer, error) { writer := func(val reflect.Value, w *encbuf) error { if val.IsNil() { return nilfunc(w) - } else { - return etypeinfo.writer(val.Elem(), w) } + return etypeinfo.writer(val.Elem(), w) } return writer, err } diff --git a/rpc/client.go b/rpc/client.go index 8aa84ec982..77b4d5ee01 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -25,6 +25,7 @@ import ( "fmt" "net" "net/url" + "os" "reflect" "strconv" "strings" @@ -171,6 +172,8 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { return DialHTTP(rawurl) case "ws", "wss": return DialWebsocket(ctx, rawurl, "") + case "stdio": + return DialStdIO(ctx) case "": return DialIPC(ctx, rawurl) default: @@ -178,13 +181,51 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { } } +type StdIOConn struct{} + +func (io StdIOConn) Read(b []byte) (n int, err error) { + return os.Stdin.Read(b) +} + +func (io StdIOConn) Write(b []byte) (n int, err error) { + return os.Stdout.Write(b) +} + +func (io StdIOConn) Close() error { + return nil +} + +func (io StdIOConn) LocalAddr() net.Addr { + return &net.UnixAddr{Name: "stdio", Net: "stdio"} +} + +func (io StdIOConn) RemoteAddr() net.Addr { + return &net.UnixAddr{Name: "stdio", Net: "stdio"} +} + +func (io StdIOConn) SetDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (io StdIOConn) SetReadDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (io StdIOConn) SetWriteDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} +func DialStdIO(ctx context.Context) (*Client, error) { + return newClient(ctx, func(_ context.Context) (net.Conn, error) { + return StdIOConn{}, nil + }) +} + func newClient(initctx context.Context, connectFunc func(context.Context) (net.Conn, error)) (*Client, error) { conn, err := connectFunc(initctx) if err != nil { return nil, err } _, isHTTP := conn.(*httpConn) - c := &Client{ writeConn: conn, isHTTP: isHTTP, @@ -524,13 +565,13 @@ func (c *Client) dispatch(conn net.Conn) { } case err := <-c.readErr: - log.Debug(fmt.Sprintf("<-readErr: %v", err)) + log.Debug("<-readErr", "err", err) c.closeRequestOps(err) conn.Close() reading = false case newconn := <-c.reconnected: - log.Debug(fmt.Sprintf("<-reconnected: (reading=%t) %v", reading, conn.RemoteAddr())) + log.Debug("<-reconnected", "reading", reading, "remote", conn.RemoteAddr()) if reading { // Wait for the previous read loop to exit. This is a rare case. conn.Close() @@ -587,7 +628,7 @@ func (c *Client) closeRequestOps(err error) { func (c *Client) handleNotification(msg *jsonrpcMessage) { if !strings.HasSuffix(msg.Method, notificationMethodSuffix) { - log.Debug(fmt.Sprint("dropping non-subscription message: ", msg)) + log.Debug("dropping non-subscription message", "msg", msg) return } var subResult struct { @@ -595,7 +636,7 @@ func (c *Client) handleNotification(msg *jsonrpcMessage) { Result json.RawMessage `json:"result"` } if err := json.Unmarshal(msg.Params, &subResult); err != nil { - log.Debug(fmt.Sprint("dropping invalid subscription message: ", msg)) + log.Debug("dropping invalid subscription message", "msg", msg) return } if c.subs[subResult.ID] != nil { @@ -606,7 +647,7 @@ func (c *Client) handleNotification(msg *jsonrpcMessage) { func (c *Client) handleResponse(msg *jsonrpcMessage) { op := c.respWait[string(msg.ID)] if op == nil { - log.Debug(fmt.Sprintf("unsolicited response %v", msg)) + log.Debug("unsolicited response", "msg", msg) return } delete(c.respWait, string(msg.ID)) diff --git a/rpc/doc.go b/rpc/doc.go index 14b3780ade..78aa92f899 100644 --- a/rpc/doc.go +++ b/rpc/doc.go @@ -92,7 +92,7 @@ An example method: Subscriptions are deleted when: - the user sends an unsubscribe request - the connection which was used to create the subscription is closed. This can be initiated - by the client and server. The server will close the connection on an write error or when + by the client and server. The server will close the connection on a write error or when the queue of buffered notifications gets too big. */ package rpc diff --git a/rpc/endpoints.go b/rpc/endpoints.go new file mode 100644 index 0000000000..692c62d3a4 --- /dev/null +++ b/rpc/endpoints.go @@ -0,0 +1,102 @@ +// Copyright 2018 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 . + +package rpc + +import ( + "net" + + "github.com/ethereum/go-ethereum/log" +) + +// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules +func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string) (net.Listener, *Server, error) { + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + handler := NewServer() + for _, api := range apis { + if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return nil, nil, err + } + log.Debug("HTTP registered", "namespace", api.Namespace) + } + } + // All APIs registered, start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, nil, err + } + go NewHTTPServer(cors, vhosts, handler).Serve(listener) + return listener, handler, err +} + +// StartWSEndpoint starts a websocket endpoint +func StartWSEndpoint(endpoint string, apis []API, modules []string, wsOrigins []string, exposeAll bool) (net.Listener, *Server, error) { + + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + handler := NewServer() + for _, api := range apis { + if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return nil, nil, err + } + log.Debug("WebSocket registered", "service", api.Service, "namespace", api.Namespace) + } + } + // All APIs registered, start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, nil, err + } + go NewWSServer(wsOrigins, handler).Serve(listener) + return listener, handler, err + +} + +// StartIPCEndpoint starts an IPC endpoint. +func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) { + // Register all the APIs exposed by the services. + handler := NewServer() + for _, api := range apis { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return nil, nil, err + } + log.Debug("IPC registered", "namespace", api.Namespace) + } + // All APIs registered, start the IPC listener. + listener, err := ipcListen(ipcEndpoint) + if err != nil { + return nil, nil, err + } + go handler.ServeListener(listener) + return listener, handler, nil +} diff --git a/rpc/http.go b/rpc/http.go index e8f51150f4..feaa7348c4 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -90,10 +90,19 @@ func DialHTTP(endpoint string) (*Client, error) { func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { hc := c.writeConn.(*httpConn) respBody, err := hc.doRequest(ctx, msg) + if respBody != nil { + defer respBody.Close() + } + if err != nil { + if respBody != nil { + buf := new(bytes.Buffer) + if _, err2 := buf.ReadFrom(respBody); err2 == nil { + return fmt.Errorf("%v %v", err, buf.String()) + } + } return err } - defer respBody.Close() var respmsg jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { return err @@ -132,6 +141,9 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos if err != nil { return nil, err } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return resp.Body, errors.New(resp.Status) + } return resp.Body, nil } @@ -169,12 +181,17 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // All checks passed, create a codec that reads direct from the request body // untilEOF and writes the response to w and order the server to process a // single request. + ctx := context.Background() + ctx = context.WithValue(ctx, "remote", r.RemoteAddr) + ctx = context.WithValue(ctx, "scheme", r.Proto) + ctx = context.WithValue(ctx, "local", r.Host) + body := io.LimitReader(r.Body, maxRequestContentLength) codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) defer codec.Close() w.Header().Set("content-type", contentType) - srv.ServeSingleRequest(codec, OptionMethodInvocation) + srv.ServeSingleRequest(ctx, codec, OptionMethodInvocation) } // validateRequest returns a non-zero response code and error message if the diff --git a/rpc/inproc.go b/rpc/inproc.go index 595a7ca651..cbe65d10e7 100644 --- a/rpc/inproc.go +++ b/rpc/inproc.go @@ -21,7 +21,7 @@ import ( "net" ) -// NewInProcClient attaches an in-process connection to the given RPC server. +// DialInProc attaches an in-process connection to the given RPC server. func DialInProc(handler *Server) *Client { initctx := context.Background() c, _ := newClient(initctx, func(context.Context) (net.Conn, error) { diff --git a/rpc/ipc.go b/rpc/ipc.go index 8de18a56fe..b05e503d74 100644 --- a/rpc/ipc.go +++ b/rpc/ipc.go @@ -18,26 +18,23 @@ package rpc import ( "context" - "fmt" "net" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/netutil" ) -// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on -// Windows this is a named pipe -func CreateIPCListener(endpoint string) (net.Listener, error) { - return ipcListen(endpoint) -} - // ServeListener accepts connections on l, serving JSON-RPC on them. func (srv *Server) ServeListener(l net.Listener) error { for { conn, err := l.Accept() - if err != nil { + if netutil.IsTemporaryError(err) { + log.Warn("RPC accept error", "err", err) + continue + } else if err != nil { return err } - log.Trace(fmt.Sprint("accepted conn", conn.RemoteAddr())) + log.Trace("Accepted connection", "addr", conn.RemoteAddr()) go srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions) } } diff --git a/rpc/server.go b/rpc/server.go index 11373b504c..7f304d8d93 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -125,7 +125,7 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error { // If singleShot is true it will process a single request, otherwise it will handle // requests until the codec returns an error when reading a request (in most cases // an EOF). It executes requests in parallel when singleShot is false. -func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecOption) error { +func (s *Server) serveRequest(ctx context.Context, codec ServerCodec, singleShot bool, options CodecOption) error { var pend sync.WaitGroup defer func() { @@ -140,7 +140,8 @@ func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecO s.codecsMu.Unlock() }() - ctx, cancel := context.WithCancel(context.Background()) + // ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() // if the codec supports notification include a notifier that callbacks can use @@ -215,14 +216,14 @@ func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecO // stopped. In either case the codec is closed. func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { defer codec.Close() - s.serveRequest(codec, false, options) + s.serveRequest(context.Background(), codec, false, options) } // ServeSingleRequest reads and processes a single RPC request from the given codec. It will not // close the codec unless a non-recoverable error has occurred. Note, this method will return after // a single request has been processed! -func (s *Server) ServeSingleRequest(codec ServerCodec, options CodecOption) { - s.serveRequest(codec, true, options) +func (s *Server) ServeSingleRequest(ctx context.Context, codec ServerCodec, options CodecOption) { + s.serveRequest(ctx, codec, true, options) } // Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish, diff --git a/sharding/collation.go b/sharding/collation.go index 5528453e46..3ce4b55e33 100644 --- a/sharding/collation.go +++ b/sharding/collation.go @@ -1,12 +1,15 @@ package sharding import ( + "fmt" + "math" "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/sha3" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/sharding/utils" ) // Collation base struct. @@ -38,6 +41,8 @@ type collationHeaderData struct { ProposerSignature []byte // the proposer's signature for calculating collation hash. } +var collationSizelimit = int64(math.Pow(float64(2), float64(20))) + // NewCollation initializes a collation and leaves it up to clients to serialize, deserialize // and provide the body and transactions upon creation. func NewCollation(header *CollationHeader, body []byte, transactions []*types.Transaction) *Collation { @@ -105,3 +110,83 @@ func (c *Collation) CalculateChunkRoot() { chunkRoot := common.BytesToHash(c.body) c.header.data.ChunkRoot = &chunkRoot } + +// CreateRawBlobs creates raw blobs from transactions. +func (c Collation) CreateRawBlobs() ([]*utils.RawBlob, error) { + + // It does not skip evm execution by default + blobs := make([]*utils.RawBlob, len(c.transactions)) + for i := 0; i < len(c.transactions); i++ { + + err := error(nil) + blobs[i], err = utils.NewRawBlob(c.transactions[i], false) + + if err != nil { + return nil, fmt.Errorf("Creation of raw blobs from transactions failed: %v", err) + } + + } + + return blobs, nil + +} + +// ConvertBackToTx converts raw blobs back to their original transactions. +func ConvertBackToTx(rawBlobs []utils.RawBlob) ([]*types.Transaction, error) { + + blobs := make([]*types.Transaction, len(rawBlobs)) + + for i := 0; i < len(rawBlobs); i++ { + + blobs[i] = types.NewTransaction(0, common.HexToAddress("0x"), nil, 0, nil, nil) + + err := utils.ConvertFromRawBlob(&rawBlobs[i], blobs[i]) + if err != nil { + return nil, fmt.Errorf("Creation of transactions from raw blobs failed: %v", err) + } + } + return blobs, nil + +} + +// Serialize method serializes the collation body to a byte array. +func (c *Collation) Serialize() ([]byte, error) { + + blobs, err := c.CreateRawBlobs() + + if err != nil { + return nil, fmt.Errorf("%v", err) + } + + serializedTx, err := utils.Serialize(blobs) + + if err != nil { + return nil, fmt.Errorf("%v", err) + } + + if int64(len(serializedTx)) > collationSizelimit { + + return nil, fmt.Errorf("The serialized body exceeded the collation size limit: %v", serializedTx) + + } + + return serializedTx, nil + +} + +// Deserialize takes a byte array and converts its back to its original transactions. +func Deserialize(serialisedBlob []byte) (*[]*types.Transaction, error) { + + deserializedBlobs, err := utils.Deserialize(serialisedBlob) + if err != nil { + return nil, fmt.Errorf("%v", err) + } + + txs, err := ConvertBackToTx(deserializedBlobs) + + if err != nil { + return nil, fmt.Errorf("%v", err) + } + + return &txs, nil +} diff --git a/sharding/collation_test.go b/sharding/collation_test.go index 2d815f3360..c0e4cb7355 100644 --- a/sharding/collation_test.go +++ b/sharding/collation_test.go @@ -1,13 +1,24 @@ package sharding import ( + "bytes" "math/big" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) +// fieldAccess is to access unexported fields in structs in another package +func fieldAccess(i interface{}, fields []string) reflect.Value { + val := reflect.ValueOf(i) + for i := 0; i < len(fields); i++ { + val = reflect.Indirect(val).FieldByName(fields[i]) + } + return val + +} func TestCollation_Transactions(t *testing.T) { header := NewCollationHeader(big.NewInt(1), nil, big.NewInt(1), nil, []byte{}) body := []byte{} @@ -27,16 +38,89 @@ func TestCollation_Transactions(t *testing.T) { } } -func TestCollation_ProposerAddress(t *testing.T) { - proposerAddr := common.StringToAddress("proposer") - header := NewCollationHeader(big.NewInt(1), nil, big.NewInt(1), &proposerAddr, []byte{}) +//TODO: Add test for converting *types.Transaction into raw blobs + +//Tests that Transactions can be serialised +func TestSerialize_Deserialize(t *testing.T) { + + header := NewCollationHeader(big.NewInt(1), nil, big.NewInt(1), nil, []byte{}) body := []byte{} - - collation := NewCollation(header, body, nil) - - if collation.ProposerAddress().String() != proposerAddr.String() { - t.Errorf("initialized collation does not contain correct proposer address") + transactions := []*types.Transaction{ + makeTxWithGasLimit(0), + makeTxWithGasLimit(5), + makeTxWithGasLimit(20), + makeTxWithGasLimit(100), } + + c := NewCollation(header, body, transactions) + + tx := c.transactions + + results, err := c.Serialize() + + if err != nil { + t.Errorf("Unable to Serialize transactions, %v", err) + } + + deserializedTxs, err := Deserialize(results) + + if err != nil { + t.Errorf("Unable to deserialize collation body, %v", err) + } + + if len(tx) != len(*deserializedTxs) { + t.Errorf("Transaction length is different before and after serialization: %v, %v", len(tx), len(*deserializedTxs)) + } + + for i := 0; i < len(tx); i++ { + + beforeSerialization := tx[i] + afterDeserialization := (*deserializedTxs)[i] + + if beforeSerialization.Nonce() != afterDeserialization.Nonce() { + + t.Errorf("Data before serialization and after deserialization are not the same ,AccountNonce: %v, %v", beforeSerialization.Nonce(), afterDeserialization.Nonce()) + + } + + if beforeSerialization.Gas() != afterDeserialization.Gas() { + + t.Errorf("Data before serialization and after deserialization are not the same ,GasLimit: %v, %v", beforeSerialization.Gas(), afterDeserialization.Gas()) + + } + + if beforeSerialization.GasPrice().Cmp(afterDeserialization.GasPrice()) != 0 { + + t.Errorf("Data before serialization and after deserialization are not the same ,Price: %v, %v", beforeSerialization.GasPrice(), afterDeserialization.GasPrice()) + + } + + beforeAddress := reflect.ValueOf(beforeSerialization.To()) + afterAddress := reflect.ValueOf(afterDeserialization.To()) + + if reflect.DeepEqual(beforeAddress, afterAddress) { + + t.Errorf("Data before serialization and after deserialization are not the same ,Recipient: %v, %v", beforeAddress, afterAddress) + + } + + if beforeSerialization.Value().Cmp(afterDeserialization.Value()) != 0 { + + t.Errorf("Data before serialization and after deserialization are not the same ,Amount: %v, %v", beforeSerialization.Value(), afterDeserialization.Value()) + + } + + beforeData := beforeSerialization.Data() + afterData := afterDeserialization.Data() + + if !bytes.Equal(beforeData, afterData) { + + t.Errorf("Data before serialization and after deserialization are not the same ,Payload: %v, %v", beforeData, afterData) + + } + + } + } func makeTxWithGasLimit(gl uint64) *types.Transaction { diff --git a/sharding/contracts/sharding_manager_test.go b/sharding/contracts/sharding_manager_test.go index feedc5398f..f58868bc8f 100644 --- a/sharding/contracts/sharding_manager_test.go +++ b/sharding/contracts/sharding_manager_test.go @@ -36,7 +36,7 @@ var ( ctx = context.Background() ) -// fastForward is a helper function to skip through n period +// fastForward is a helper function to skip through n period. func fastForward(backend *backends.SimulatedBackend, p int) { for i := 0; i < p*int(sharding.PeriodLength); i++ { backend.Commit() @@ -231,7 +231,7 @@ func TestNotaryDoubleRegisters(t *testing.T) { t.Fatalf("Notary pool length mismatched: %v", err) } - // Notary 0 registers again. This time should fail + // Notary 0 registers again, This time should fail. if err = s.registerNotaries(big.NewInt(0), 0, 1); err == nil { t.Errorf("Notary register should have failed with double registers") } @@ -423,7 +423,7 @@ func TestGetCommitteeWithNonMember(t *testing.T) { } } -// TestGetCommitteeWithinSamePeriod tests notary registers and samples within the same period +// TestGetCommitteeWithinSamePeriod tests notary registers and samples within the same period. func TestGetCommitteeWithinSamePeriod(t *testing.T) { s, _ := newSMCTestHelper(1) @@ -437,7 +437,7 @@ func TestGetCommitteeWithinSamePeriod(t *testing.T) { t.Fatalf("Notary pool length mismatched: %v", err) } - // Notary 0 samples for itself within the same period after registration + // Notary 0 samples for itself within the same period after registration. sampledAddr, _ := s.smc.GetNotaryInCommittee(&bind.CallOpts{}, big.NewInt(0)) if s.testAccounts[0].addr != sampledAddr { t.Errorf("Unable to sample notary address within same period of registration, got addr: %v", sampledAddr) @@ -565,7 +565,7 @@ func TestSubmitVote(t *testing.T) { t.Errorf("Incorrect notary vote count, want: 0, got: %v", c) } - // Notary votes on the header that was submitted + // Notary votes on the header that was submitted. err = s.submitVote(&s.testAccounts[0], shard0, period1, index0, 'A') if err != nil { t.Fatalf("Notary submits vote failed: %v", err) @@ -642,7 +642,7 @@ func TestSubmitVoteByNonEligibleNotary(t *testing.T) { t.Errorf("Non registered notary submits vote should have failed") } - // Check notary's vote count is correct in shard + // Check notary's vote count is correct in shard. c, _ := s.smc.GetVoteCount(&bind.CallOpts{}, shard0) if c.Cmp(big.NewInt(0)) != 0 { t.Errorf("Incorrect notary vote count, want: 0, got: %v", c) diff --git a/sharding/database/inmemory_test.go b/sharding/database/inmemory_test.go index 67c1d11d1a..2f9a54dae9 100644 --- a/sharding/database/inmemory_test.go +++ b/sharding/database/inmemory_test.go @@ -12,7 +12,7 @@ var _ = sharding.ShardBackend(&ShardKV{}) func Test_ShardKVPut(t *testing.T) { kv := NewShardKV() - hash := common.StringToHash("ralph merkle") + hash := common.BytesToHash([]byte("ralph merkle")) if err := kv.Put(hash, []byte{1, 2, 3}); err != nil { t.Errorf("could not save value in kv store: %v", err) @@ -21,7 +21,7 @@ func Test_ShardKVPut(t *testing.T) { func Test_ShardKVHas(t *testing.T) { kv := NewShardKV() - hash := common.StringToHash("ralph merkle") + hash := common.BytesToHash([]byte("ralph merkle")) if err := kv.Put(hash, []byte{1, 2, 3}); err != nil { t.Fatalf("could not save value in kv store: %v", err) @@ -31,7 +31,7 @@ func Test_ShardKVHas(t *testing.T) { t.Errorf("kv store does not have hash: %v", hash) } - hash2 := common.StringToHash("") + hash2 := common.BytesToHash([]byte{}) if kv.Has(hash2) { t.Errorf("kv store should not contain unset key: %v", hash2) } @@ -39,7 +39,7 @@ func Test_ShardKVHas(t *testing.T) { func Test_ShardKVGet(t *testing.T) { kv := NewShardKV() - hash := common.StringToHash("ralph merkle") + hash := common.BytesToHash([]byte("ralph merkle")) if err := kv.Put(hash, []byte{1, 2, 3}); err != nil { t.Fatalf("could not save value in kv store: %v", err) @@ -53,7 +53,7 @@ func Test_ShardKVGet(t *testing.T) { t.Errorf("no value stored for key") } - hash2 := common.StringToHash("") + hash2 := common.BytesToHash([]byte{}) val2, err := kv.Get(hash2) if val2 != nil { t.Errorf("non-existent key should not have a value. key=%v, value=%v", hash2, val2) @@ -62,7 +62,7 @@ func Test_ShardKVGet(t *testing.T) { func Test_ShardKVDelete(t *testing.T) { kv := NewShardKV() - hash := common.StringToHash("ralph merkle") + hash := common.BytesToHash([]byte("ralph merkle")) if err := kv.Put(hash, []byte{1, 2, 3}); err != nil { t.Fatalf("could not save value in kv store: %v", err) diff --git a/sharding/shard_test.go b/sharding/shard_test.go index 710c4e4888..e4258c38d2 100644 --- a/sharding/shard_test.go +++ b/sharding/shard_test.go @@ -40,8 +40,8 @@ func (c *Collation) Hash() (hash common.Hash) { return hash } func TestShard_ValidateShardID(t *testing.T) { - emptyHash := common.StringToHash("") - emptyAddr := common.StringToAddress("") + emptyHash := common.BytesToHash([]byte{}) + emptyAddr := common.BytesToAddress([]byte{}) header := NewCollationHeader(big.NewInt(1), &emptyHash, big.NewInt(1), &emptyAddr, []byte{}) shardDB := database.NewShardKV() shard := NewShard(big.NewInt(3), shardDB) @@ -59,8 +59,8 @@ func TestShard_ValidateShardID(t *testing.T) { } func TestShard_HeaderByHash(t *testing.T) { - emptyHash := common.StringToHash("") - emptyAddr := common.StringToAddress("") + emptyHash := common.BytesToHash([]byte{}) + emptyAddr := common.BytesToAddress([]byte{}) header := NewCollationHeader(big.NewInt(1), &emptyHash, big.NewInt(1), &emptyAddr, []byte{}) // creates a mockDB that always returns nil values from .Get and errors in every other method. @@ -96,7 +96,7 @@ func TestShard_HeaderByHash(t *testing.T) { } func TestShard_CollationByHash(t *testing.T) { - emptyAddr := common.StringToAddress("") + emptyAddr := common.BytesToAddress([]byte{}) // Empty chunk root. header := NewCollationHeader(big.NewInt(1), nil, big.NewInt(1), &emptyAddr, []byte{}) @@ -156,7 +156,7 @@ func TestShard_CollationByHash(t *testing.T) { func TestShard_CanonicalHeaderHash(t *testing.T) { shardID := big.NewInt(1) period := big.NewInt(1) - proposerAddress := common.StringToAddress("") + proposerAddress := common.BytesToAddress([]byte{}) proposerSignature := []byte{} header := NewCollationHeader(shardID, nil, period, &proposerAddress, proposerSignature) @@ -199,9 +199,9 @@ func TestShard_CanonicalHeaderHash(t *testing.T) { func TestShard_CanonicalCollation(t *testing.T) { shardID := big.NewInt(1) period := big.NewInt(1) - proposerAddress := common.StringToAddress("") + proposerAddress := common.BytesToAddress([]byte{}) proposerSignature := []byte{} - emptyHash := common.StringToHash("") + emptyHash := common.BytesToHash([]byte{}) header := NewCollationHeader(shardID, &emptyHash, period, &proposerAddress, proposerSignature) shardDB := database.NewShardKV() @@ -241,7 +241,7 @@ func TestShard_CanonicalCollation(t *testing.T) { } func TestShard_SetCanonical(t *testing.T) { - chunkRoot := common.StringToHash("") + chunkRoot := common.BytesToHash([]byte{}) header := NewCollationHeader(big.NewInt(1), &chunkRoot, big.NewInt(1), nil, []byte{}) shardDB := database.NewShardKV() @@ -285,7 +285,7 @@ func TestShard_BodyByChunkRoot(t *testing.T) { } // it should throw error if fetching non-existent chunk root. - emptyHash := common.StringToHash("") + emptyHash := common.BytesToHash([]byte{}) if _, err := shard.BodyByChunkRoot(&emptyHash); err == nil { t.Errorf("non-existent chunk root should throw error: %v", err) } @@ -307,9 +307,9 @@ func TestShard_BodyByChunkRoot(t *testing.T) { func TestShard_CheckAvailability(t *testing.T) { shardID := big.NewInt(1) period := big.NewInt(1) - proposerAddress := common.StringToAddress("") + proposerAddress := common.BytesToAddress([]byte{}) proposerSignature := []byte{} - emptyHash := common.StringToHash("") + emptyHash := common.BytesToHash([]byte{}) header := NewCollationHeader(shardID, &emptyHash, period, &proposerAddress, proposerSignature) shardDB := database.NewShardKV() @@ -342,7 +342,7 @@ func TestShard_CheckAvailability(t *testing.T) { } func TestShard_SetAvailability(t *testing.T) { - chunkRoot := common.StringToHash("") + chunkRoot := common.BytesToHash([]byte{}) header := NewCollationHeader(big.NewInt(1), &chunkRoot, big.NewInt(1), nil, []byte{}) // creates a mockDB that always returns nil values from .Get and errors in every other method. @@ -378,9 +378,9 @@ func TestShard_SetAvailability(t *testing.T) { func TestShard_SaveCollation(t *testing.T) { headerShardID := big.NewInt(1) period := big.NewInt(1) - proposerAddress := common.StringToAddress("") + proposerAddress := common.BytesToAddress([]byte{}) proposerSignature := []byte{} - emptyHash := common.StringToHash("") + emptyHash := common.BytesToHash([]byte{}) header := NewCollationHeader(headerShardID, &emptyHash, period, &proposerAddress, proposerSignature) shardDB := database.NewShardKV() @@ -402,7 +402,7 @@ func TestShard_SaveCollation(t *testing.T) { func TestShard_SaveHeader(t *testing.T) { // creates a mockDB that always returns nil values from .Get and errors in every other method. mockDB := &mockShardDB{kv: make(map[common.Hash]*[]byte)} - emptyHash := common.StringToHash("") + emptyHash := common.BytesToHash([]byte{}) errorShard := NewShard(big.NewInt(1), mockDB) header := NewCollationHeader(big.NewInt(1), &emptyHash, big.NewInt(1), nil, []byte{}) diff --git a/sharding/utils/marshal.go b/sharding/utils/marshal.go new file mode 100644 index 0000000000..cddf61333e --- /dev/null +++ b/sharding/utils/marshal.go @@ -0,0 +1,194 @@ +package utils + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + chunkSize = int64(32) + indicatorSize = int64(1) + chunkDataSize = chunkSize - indicatorSize +) + +// Flags to add to chunk delimiter. +type Flags struct { + skipEvmExecution bool +} + +// RawBlob type which will contain flags and data for serialization. +type RawBlob struct { + flags Flags + data []byte +} + +// NewRawBlob builds a raw blob from any interface by using +// RLP encoding. +func NewRawBlob(i interface{}, skipEvm bool) (*RawBlob, error) { + data, err := rlp.EncodeToBytes(i) + if err != nil { + return nil, fmt.Errorf("RLP encoding was a failure:%v", err) + } + return &RawBlob{data: data, flags: Flags{skipEvmExecution: skipEvm}}, nil +} + +// ConvertFromRawBlob converts raw blob back from a byte array +// to its interface. +func ConvertFromRawBlob(blob *RawBlob, i interface{}) error { + data := (*blob).data + err := rlp.DecodeBytes(data, i) + if err != nil { + return fmt.Errorf("RLP decoding was a failure:%v", err) + } + + return nil +} + +// SerializeBlob parses the blob and serializes it appropriately. +func SerializeBlob(cb RawBlob) ([]byte, error) { + + length := int64(len(cb.data)) + terminalLength := length % chunkDataSize + chunksNumber := length / chunkDataSize + indicatorByte := make([]byte, 1) + indicatorByte[0] = 0 + if cb.flags.skipEvmExecution { + indicatorByte[0] |= (1 << 7) + } + tempBody := []byte{} + + // if blob is less than 31 bytes, adds the indicator chunk + // and pads the remaining empty bytes to the right. + if chunksNumber == 0 { + paddedBytes := make([]byte, (chunkDataSize - length)) + indicatorByte[0] = byte(terminalLength) + if cb.flags.skipEvmExecution { + indicatorByte[0] |= (1 << 7) + } + tempBody = append(indicatorByte, append(cb.data, paddedBytes...)...) + return tempBody, nil + } + + // if there is no need to pad empty bytes, then the indicator byte + // is added as 0001111, then this chunk is returned to the + // main Serialize function. + if terminalLength == 0 { + + for i := int64(1); i < chunksNumber; i++ { + // This loop loops through all non-terminal chunks and add a indicator + // byte of 00000000, each chunk is created by appending the indicator + // byte to the data chunks. The data chunks are separated into sets of + // 31 bytes. + + tempBody = append(tempBody, + append(indicatorByte, + cb.data[(i-1)*chunkDataSize:i*chunkDataSize]...)...) + + } + indicatorByte[0] = byte(chunkDataSize) + if cb.flags.skipEvmExecution { + indicatorByte[0] |= (1 << 7) + } + + // Terminal chunk has its indicator byte added, chunkDataSize*chunksNumber refers to the total size of the blob + tempBody = append(tempBody, + append(indicatorByte, + cb.data[(chunksNumber-1)*chunkDataSize:chunkDataSize*chunksNumber]...)...) + + return tempBody, nil + + } + + // This loop loops through all non-terminal chunks and add a indicator byte + // of 00000000, each chunk is created by appending the indcator byte + // to the data chunks. The data chunks are separated into sets of 31. + for i := int64(1); i <= chunksNumber; i++ { + tempBody = append(tempBody, + append(indicatorByte, + cb.data[(i-1)*chunkDataSize:i*chunkDataSize]...)...) + } + // Appends indicator bytes to terminal-chunks , and if the index of the chunk + // delimiter is non-zero adds it to the chunk. Also pads empty bytes to + // the terminal chunk.chunkDataSize*chunksNumber refers to the total + // size of the blob. finalchunkIndex refers to the index of the last data byte. + indicatorByte[0] = byte(terminalLength) + if cb.flags.skipEvmExecution { + indicatorByte[0] |= (1 << 7) + } + tempBody = append(tempBody, + append(indicatorByte, + cb.data[chunkDataSize*chunksNumber:length]...)...) + + emptyBytes := make([]byte, (chunkDataSize - terminalLength)) + tempBody = append(tempBody, emptyBytes...) + + return tempBody, nil + +} + +// Serialize takes a set of blobs and converts them to a single byte array. +func Serialize(rawblobs []*RawBlob) ([]byte, error) { + length := int64(len(rawblobs)) + + serialisedData := []byte{} + + //Loops through all the blobs and serializes them into chunks + for i := int64(0); i < length; i++ { + data := *rawblobs[i] + refinedData, err := SerializeBlob(data) + if err != nil { + return nil, fmt.Errorf("Index %v: %v", i, err) + } + serialisedData = append(serialisedData, refinedData...) + } + + return serialisedData, nil +} + +// Deserialize results in the byte array being deserialised and +// separated into its respective interfaces. +func Deserialize(data []byte) ([]RawBlob, error) { + + length := int64(len(data)) + chunksNumber := length / chunkSize + indicatorByte := byte(0) + tempBody := RawBlob{} + var deserializedBlob []RawBlob + + // This separates the byte array into its separate blobs. + for i := int64(1); i <= chunksNumber; i++ { + indicatorIndex := (i - 1) * chunkSize + + // Tests if the chunk delimiter is zero, if it is it will append the data chunk + // to tempBody. + if data[indicatorIndex] == indicatorByte || data[indicatorIndex] == byte(128) { + tempBody.data = append(tempBody.data, data[(indicatorIndex+1):(i)*chunkSize]...) + + } else if data[indicatorIndex] == byte(31) || data[indicatorIndex] == byte(159) { + if data[indicatorIndex] == byte(159) { + tempBody.flags.skipEvmExecution = true + } + tempBody.data = append(tempBody.data, data[(indicatorIndex+1):indicatorIndex+1+chunkDataSize]...) + deserializedBlob = append(deserializedBlob, tempBody) + tempBody = RawBlob{} + + } else { + // Since the chunk delimiter in non-zero now we can infer that it is + // a terminal chunk and add it and append to the deserializedblob + // slice. The tempBody signifies a single deserialized blob. + terminalIndex := int64(data[indicatorIndex]) + //Check if EVM flag is equal to 1 + flagindex := data[indicatorIndex] >> 7 + if flagindex == byte(1) { + terminalIndex = int64(data[indicatorIndex]) - 128 + tempBody.flags.skipEvmExecution = true + } + tempBody.data = append(tempBody.data, data[(indicatorIndex+1):(indicatorIndex+1+terminalIndex)]...) + deserializedBlob = append(deserializedBlob, tempBody) + tempBody = RawBlob{} + } + } + + return deserializedBlob, nil +} diff --git a/sharding/utils/marshal_test.go b/sharding/utils/marshal_test.go new file mode 100644 index 0000000000..a16013f54b --- /dev/null +++ b/sharding/utils/marshal_test.go @@ -0,0 +1,88 @@ +package utils + +import ( + "math/rand" + "reflect" + "testing" +) + +func buildRawBlob(size int64) []RawBlob { + tempbody := make([]RawBlob, size) + for i := int64(0); i < size; i++ { + var rawblob RawBlob + rawblob.data = buildBlob(size) + flagset := byte(rand.Int()) >> 7 + if flagset == byte(1) { + rawblob.flags.skipEvmExecution = true + + } + tempbody[i] = rawblob + } + + return tempbody +} + +func buildBlob(size int64) []byte { + tempbody := make([]byte, size) + for i := int64(0); i < size; i++ { + tempbody[i] = byte(rand.Int()) + } + + return tempbody +} + +func TestSize(t *testing.T) { + for i := 0; i < 300; i++ { + size := int64(i) + blob := buildRawBlob(size) + chunksafterSerialize := size / chunkDataSize + terminalchunk := size % chunkDataSize + if terminalchunk != 0 { + chunksafterSerialize = chunksafterSerialize + 1 + } + chunksafterSerialize = chunksafterSerialize * size + sizeafterSerialize := chunksafterSerialize * chunkSize + + drefbody := make([]*RawBlob, len(blob)) + for s := 0; s < len(blob); s++ { + drefbody[s] = &(blob[s]) + + } + serializedblob, err := Serialize(drefbody) + if err != nil { + t.Errorf("Error Serializing blob:%v\n %v", err, serializedblob) + } + + if int64(len(serializedblob)) != sizeafterSerialize { + t.Errorf("Error Serializing blobs the lengths are not the same:\n %d \n %d", int64(len(serializedblob)), sizeafterSerialize) + } + } + +} +func TestSerializeAndDeserializeblob(t *testing.T) { + + for i := 1; i < 300; i++ { + + blob := buildRawBlob(int64(i)) + + drefbody := make([]*RawBlob, len(blob)) + for s := 0; s < len(blob); s++ { + drefbody[s] = &(blob[s]) + } + + serializedblob, err := Serialize(drefbody) + + if err != nil { + t.Errorf("Error Serializing blob at index %d:\n%v\n%v", i, err, serializedblob) + } + raw, err2 := Deserialize(serializedblob) + if err2 != nil { + t.Errorf("Error Serializing blob at index %d:\n%v due to \n%v", i, raw, err2) + } + + if !reflect.DeepEqual(blob, raw) { + t.Errorf("Error Serializing blobs at index %d, the serialized and deserialized versions are not the same:\n\n %v \n\n %v \n\n %v", i, blob, serializedblob, raw) + } + } + +} diff --git a/signer/core/abihelper.go b/signer/core/abihelper.go new file mode 100644 index 0000000000..1d4fbc7dc2 --- /dev/null +++ b/signer/core/abihelper.go @@ -0,0 +1,255 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "bytes" + "os" + "regexp" +) + +type decodedArgument struct { + soltype abi.Argument + value interface{} +} +type decodedCallData struct { + signature string + name string + inputs []decodedArgument +} + +// String implements stringer interface, tries to use the underlying value-type +func (arg decodedArgument) String() string { + var value string + switch arg.value.(type) { + case fmt.Stringer: + value = arg.value.(fmt.Stringer).String() + default: + value = fmt.Sprintf("%v", arg.value) + } + return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value) +} + +// String implements stringer interface for decodedCallData +func (cd decodedCallData) String() string { + args := make([]string, len(cd.inputs)) + for i, arg := range cd.inputs { + args[i] = arg.String() + } + return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ",")) +} + +// parseCallData matches the provided call data against the abi definition, +// and returns a struct containing the actual go-typed values +func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) { + + if len(calldata) < 4 { + return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata)) + } + + sigdata, argdata := calldata[:4], calldata[4:] + if len(argdata)%32 != 0 { + return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata)) + } + + abispec, err := abi.JSON(strings.NewReader(abidata)) + if err != nil { + return nil, fmt.Errorf("Failed parsing JSON ABI: %v, abidata: %v", err, abidata) + } + + method, err := abispec.MethodById(sigdata) + if err != nil { + return nil, err + } + + v, err := method.Inputs.UnpackValues(argdata) + if err != nil { + return nil, err + } + + decoded := decodedCallData{signature: method.Sig(), name: method.Name} + + for n, argument := range method.Inputs { + if err != nil { + return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err) + } + decodedArg := decodedArgument{ + soltype: argument, + value: v[n], + } + decoded.inputs = append(decoded.inputs, decodedArg) + } + + // We're finished decoding the data. At this point, we encode the decoded data to see if it matches with the + // original data. If we didn't do that, it would e.g. be possible to stuff extra data into the arguments, which + // is not detected by merely decoding the data. + + var ( + encoded []byte + ) + encoded, err = method.Inputs.PackValues(v) + + if err != nil { + return nil, err + } + + if !bytes.Equal(encoded, argdata) { + was := common.Bytes2Hex(encoded) + exp := common.Bytes2Hex(argdata) + return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig()) + } + return &decoded, nil +} + +// MethodSelectorToAbi converts a method selector into an ABI struct. The returned data is a valid json string +// which can be consumed by the standard abi package. +func MethodSelectorToAbi(selector string) ([]byte, error) { + + re := regexp.MustCompile(`^([^\)]+)\(([a-z0-9,\[\]]*)\)`) + + type fakeArg struct { + Type string `json:"type"` + } + type fakeABI struct { + Name string `json:"name"` + Type string `json:"type"` + Inputs []fakeArg `json:"inputs"` + } + groups := re.FindStringSubmatch(selector) + if len(groups) != 3 { + return nil, fmt.Errorf("Did not match: %v (%v matches)", selector, len(groups)) + } + name := groups[1] + args := groups[2] + arguments := make([]fakeArg, 0) + if len(args) > 0 { + for _, arg := range strings.Split(args, ",") { + arguments = append(arguments, fakeArg{arg}) + } + } + abicheat := fakeABI{ + name, "function", arguments, + } + return json.Marshal([]fakeABI{abicheat}) + +} + +type AbiDb struct { + db map[string]string + customdb map[string]string + customdbPath string +} + +// NewEmptyAbiDB exists for test purposes +func NewEmptyAbiDB() (*AbiDb, error) { + return &AbiDb{make(map[string]string), make(map[string]string), ""}, nil +} + +// NewAbiDBFromFile loads signature database from file, and +// errors if the file is not valid json. Does no other validation of contents +func NewAbiDBFromFile(path string) (*AbiDb, error) { + raw, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + db, err := NewEmptyAbiDB() + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.db) + return db, nil +} + +// NewAbiDBFromFiles loads both the standard signature database and a custom database. The latter will be used +// to write new values into if they are submitted via the API +func NewAbiDBFromFiles(standard, custom string) (*AbiDb, error) { + + db := &AbiDb{make(map[string]string), make(map[string]string), custom} + db.customdbPath = custom + + raw, err := ioutil.ReadFile(standard) + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.db) + // Custom file may not exist. Will be created during save, if needed + if _, err := os.Stat(custom); err == nil { + raw, err = ioutil.ReadFile(custom) + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.customdb) + } + + return db, nil +} + +// LookupMethodSelector checks the given 4byte-sequence against the known ABI methods. +// OBS: This method does not validate the match, it's assumed the caller will do so +func (db *AbiDb) LookupMethodSelector(id []byte) (string, error) { + if len(id) < 4 { + return "", fmt.Errorf("Expected 4-byte id, got %d", len(id)) + } + sig := common.ToHex(id[:4]) + if key, exists := db.db[sig]; exists { + return key, nil + } + if key, exists := db.customdb[sig]; exists { + return key, nil + } + return "", fmt.Errorf("Signature %v not found", sig) +} +func (db *AbiDb) Size() int { + return len(db.db) +} + +// saveCustomAbi saves a signature ephemerally. If custom file is used, also saves to disk +func (db *AbiDb) saveCustomAbi(selector, signature string) error { + db.customdb[signature] = selector + if db.customdbPath == "" { + return nil //Not an error per se, just not used + } + d, err := json.Marshal(db.customdb) + if err != nil { + return err + } + err = ioutil.WriteFile(db.customdbPath, d, 0600) + return err +} + +// AddSignature to the database, if custom database saving is enabled. +// OBS: This method does _not_ validate the correctness of the data, +// it is assumed that the caller has already done so +func (db *AbiDb) AddSignature(selector string, data []byte) error { + if len(data) < 4 { + return nil + } + _, err := db.LookupMethodSelector(data[:4]) + if err == nil { + return nil + } + sig := common.ToHex(data[:4]) + return db.saveCustomAbi(selector, sig) +} diff --git a/signer/core/abihelper_test.go b/signer/core/abihelper_test.go new file mode 100644 index 0000000000..8bb5776691 --- /dev/null +++ b/signer/core/abihelper_test.go @@ -0,0 +1,247 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "fmt" + "strings" + "testing" + + "io/ioutil" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func verify(t *testing.T, jsondata, calldata string, exp []interface{}) { + + abispec, err := abi.JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + cd := common.Hex2Bytes(calldata) + sigdata, argdata := cd[:4], cd[4:] + method, err := abispec.MethodById(sigdata) + + if err != nil { + t.Fatal(err) + } + + data, err := method.Inputs.UnpackValues(argdata) + + if len(data) != len(exp) { + t.Fatalf("Mismatched length, expected %d, got %d", len(exp), len(data)) + } + for i, elem := range data { + if !reflect.DeepEqual(elem, exp[i]) { + t.Fatalf("Unpack error, arg %d, got %v, want %v", i, elem, exp[i]) + } + } +} +func TestNewUnpacker(t *testing.T) { + type unpackTest struct { + jsondata string + calldata string + exp []interface{} + } + testcases := []unpackTest{ + { // https://solidity.readthedocs.io/en/develop/abi-spec.html#use-of-dynamic-types + `[{"type":"function","name":"f", "inputs":[{"type":"uint256"},{"type":"uint32[]"},{"type":"bytes10"},{"type":"bytes"}]}]`, + // 0x123, [0x456, 0x789], "1234567890", "Hello, world!" + "8be65246" + "00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000", + []interface{}{ + big.NewInt(0x123), + []uint32{0x456, 0x789}, + [10]byte{49, 50, 51, 52, 53, 54, 55, 56, 57, 48}, + common.Hex2Bytes("48656c6c6f2c20776f726c6421"), + }, + }, { // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples + `[{"type":"function","name":"sam","inputs":[{"type":"bytes"},{"type":"bool"},{"type":"uint256[]"}]}]`, + // "dave", true and [1,2,3] + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + []interface{}{ + []byte{0x64, 0x61, 0x76, 0x65}, + true, + []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + }, { + `[{"type":"function","name":"send","inputs":[{"type":"uint256"}]}]`, + "a52c101e0000000000000000000000000000000000000000000000000000000000000012", + []interface{}{big.NewInt(0x12)}, + }, { + `[{"type":"function","name":"compareAndApprove","inputs":[{"type":"address"},{"type":"uint256"},{"type":"uint256"}]}]`, + "751e107900000000000000000000000000000133700000deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + []interface{}{ + common.HexToAddress("0x00000133700000deadbeef000000000000000000"), + new(big.Int).SetBytes([]byte{0x00}), + big.NewInt(0x1), + }, + }, + } + for _, c := range testcases { + verify(t, c.jsondata, c.calldata, c.exp) + } + +} + +/* +func TestReflect(t *testing.T) { + a := big.NewInt(0) + b := new(big.Int).SetBytes([]byte{0x00}) + if !reflect.DeepEqual(a, b) { + t.Fatalf("Nope, %v != %v", a, b) + } +} +*/ + +func TestCalldataDecoding(t *testing.T) { + + // send(uint256) : a52c101e + // compareAndApprove(address,uint256,uint256) : 751e1079 + // issue(address[],uint256) : 42958b54 + jsondata := ` +[ + {"type":"function","name":"send","inputs":[{"name":"a","type":"uint256"}]}, + {"type":"function","name":"compareAndApprove","inputs":[{"name":"a","type":"address"},{"name":"a","type":"uint256"},{"name":"a","type":"uint256"}]}, + {"type":"function","name":"issue","inputs":[{"name":"a","type":"address[]"},{"name":"a","type":"uint256"}]}, + {"type":"function","name":"sam","inputs":[{"name":"a","type":"bytes"},{"name":"a","type":"bool"},{"name":"a","type":"uint256[]"}]} +]` + //Expected failures + for _, hexdata := range []string{ + "a52c101e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + "a52c101e000000000000000000000000000000000000000000000000000000000000001200", + "a52c101e00000000000000000000000000000000000000000000000000000000000000", + "a52c101e", + "a52c10", + "", + // Too short + "751e10790000000000000000000000000000000000000000000000000000000000000012", + "751e1079FFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + //Not valid multiple of 32 + "deadbeef00000000000000000000000000000000000000000000000000000000000000", + //Too short 'issue' + "42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + // Too short compareAndApprove + "a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // contains a bool with illegal values + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + } { + _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) + if err == nil { + t.Errorf("Expected decoding to fail: %s", hexdata) + } + } + + //Expected success + for _, hexdata := range []string{ + // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + "a52c101e0000000000000000000000000000000000000000000000000000000000000012", + "a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "751e1079000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "42958b54" + + // start of dynamic type + "0000000000000000000000000000000000000000000000000000000000000040" + + //uint256 + "0000000000000000000000000000000000000000000000000000000000000001" + + // length of array + "0000000000000000000000000000000000000000000000000000000000000002" + + // array values + "000000000000000000000000000000000000000000000000000000000000dead" + + "000000000000000000000000000000000000000000000000000000000000beef", + } { + _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) + if err != nil { + t.Errorf("Unexpected failure on input %s:\n %v (%d bytes) ", hexdata, err, len(common.Hex2Bytes(hexdata))) + } + } +} + +func TestSelectorUnmarshalling(t *testing.T) { + var ( + db *AbiDb + err error + abistring []byte + abistruct abi.ABI + ) + + db, err = NewAbiDBFromFile("../../cmd/clef/4byte.json") + if err != nil { + t.Fatal(err) + } + fmt.Printf("DB size %v\n", db.Size()) + for id, selector := range db.db { + + abistring, err = MethodSelectorToAbi(selector) + if err != nil { + t.Error(err) + return + } + abistruct, err = abi.JSON(strings.NewReader(string(abistring))) + if err != nil { + t.Error(err) + return + } + m, err := abistruct.MethodById(common.Hex2Bytes(id[2:])) + if err != nil { + t.Error(err) + return + } + if m.Sig() != selector { + t.Errorf("Expected equality: %v != %v", m.Sig(), selector) + } + } + +} + +func TestCustomABI(t *testing.T) { + d, err := ioutil.TempDir("", "signer-4byte-test") + if err != nil { + t.Fatal(err) + } + filename := fmt.Sprintf("%s/4byte_custom.json", d) + abidb, err := NewAbiDBFromFiles("../../cmd/clef/4byte.json", filename) + if err != nil { + t.Fatal(err) + } + // Now we'll remove all existing signatures + abidb.db = make(map[string]string) + calldata := common.Hex2Bytes("a52c101edeadbeef") + _, err = abidb.LookupMethodSelector(calldata) + if err == nil { + t.Fatalf("Should not find a match on empty db") + } + if err = abidb.AddSignature("send(uint256)", calldata); err != nil { + t.Fatalf("Failed to save file: %v", err) + } + _, err = abidb.LookupMethodSelector(calldata) + if err != nil { + t.Fatalf("Should find a match for abi signature, got: %v", err) + } + //Check that it wrote to file + abidb2, err := NewAbiDBFromFile(filename) + if err != nil { + t.Fatalf("Failed to create new abidb: %v", err) + } + _, err = abidb2.LookupMethodSelector(calldata) + if err != nil { + t.Fatalf("Save failed: should find a match for abi signature after loading from disk") + } +} diff --git a/signer/core/api.go b/signer/core/api.go new file mode 100644 index 0000000000..45933284bf --- /dev/null +++ b/signer/core/api.go @@ -0,0 +1,500 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/usbwallet" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// ExternalAPI defines the external API through which signing requests are made. +type ExternalAPI interface { + // List available accounts + List(ctx context.Context) (Accounts, error) + // New request to create a new account + New(ctx context.Context) (accounts.Account, error) + // SignTransaction request to sign the specified transaction + SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) + // Sign - request to sign the given data (plus prefix) + Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // EcRecover - request to perform ecrecover + EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) + // Export - request to export an account + Export(ctx context.Context, addr common.Address) (json.RawMessage, error) + // Import - request to import an account + Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) +} + +// SignerUI specifies what method a UI needs to implement to be able to be used as a UI for the signer +type SignerUI interface { + // ApproveTx prompt the user for confirmation to request to sign Transaction + ApproveTx(request *SignTxRequest) (SignTxResponse, error) + // ApproveSignData prompt the user for confirmation to request to sign data + ApproveSignData(request *SignDataRequest) (SignDataResponse, error) + // ApproveExport prompt the user for confirmation to export encrypted Account json + ApproveExport(request *ExportRequest) (ExportResponse, error) + // ApproveImport prompt the user for confirmation to import Account json + ApproveImport(request *ImportRequest) (ImportResponse, error) + // ApproveListing prompt the user for confirmation to list accounts + // the list of accounts to list can be modified by the UI + ApproveListing(request *ListRequest) (ListResponse, error) + // ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller + ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) + // ShowError displays error message to user + ShowError(message string) + // ShowInfo displays info message to user + ShowInfo(message string) + // OnApprovedTx notifies the UI about a transaction having been successfully signed. + // This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient. + OnApprovedTx(tx ethapi.SignTransactionResult) + // OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version + // information + OnSignerStartup(info StartupInfo) +} + +// SignerAPI defines the actual implementation of ExternalAPI +type SignerAPI struct { + chainID *big.Int + am *accounts.Manager + UI SignerUI + validator *Validator +} + +// Metadata about a request +type Metadata struct { + Remote string `json:"remote"` + Local string `json:"local"` + Scheme string `json:"scheme"` +} + +// MetadataFromContext extracts Metadata from a given context.Context +func MetadataFromContext(ctx context.Context) Metadata { + m := Metadata{"NA", "NA", "NA"} // batman + + if v := ctx.Value("remote"); v != nil { + m.Remote = v.(string) + } + if v := ctx.Value("scheme"); v != nil { + m.Scheme = v.(string) + } + if v := ctx.Value("local"); v != nil { + m.Local = v.(string) + } + return m +} + +// String implements Stringer interface +func (m Metadata) String() string { + s, err := json.Marshal(m) + if err == nil { + return string(s) + } + return err.Error() +} + +// types for the requests/response types between signer and UI +type ( + // SignTxRequest contains info about a Transaction to sign + SignTxRequest struct { + Transaction SendTxArgs `json:"transaction"` + Callinfo []ValidationInfo `json:"call_info"` + Meta Metadata `json:"meta"` + } + // SignTxResponse result from SignTxRequest + SignTxResponse struct { + //The UI may make changes to the TX + Transaction SendTxArgs `json:"transaction"` + Approved bool `json:"approved"` + Password string `json:"password"` + } + // ExportRequest info about query to export accounts + ExportRequest struct { + Address common.Address `json:"address"` + Meta Metadata `json:"meta"` + } + // ExportResponse response to export-request + ExportResponse struct { + Approved bool `json:"approved"` + } + // ImportRequest info about request to import an Account + ImportRequest struct { + Meta Metadata `json:"meta"` + } + ImportResponse struct { + Approved bool `json:"approved"` + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` + } + SignDataRequest struct { + Address common.MixedcaseAddress `json:"address"` + Rawdata hexutil.Bytes `json:"raw_data"` + Message string `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` + } + SignDataResponse struct { + Approved bool `json:"approved"` + Password string + } + NewAccountRequest struct { + Meta Metadata `json:"meta"` + } + NewAccountResponse struct { + Approved bool `json:"approved"` + Password string `json:"password"` + } + ListRequest struct { + Accounts []Account `json:"accounts"` + Meta Metadata `json:"meta"` + } + ListResponse struct { + Accounts []Account `json:"accounts"` + } + Message struct { + Text string `json:"text"` + } + StartupInfo struct { + Info map[string]interface{} `json:"info"` + } +) + +var ErrRequestDenied = errors.New("Request denied") + +type errorWrapper struct { + msg string + err error +} + +func (ew errorWrapper) String() string { + return fmt.Sprintf("%s\n%s", ew.msg, ew.err) +} + +// NewSignerAPI creates a new API that can be used for Account management. +// ksLocation specifies the directory where to store the password protected private +// key that is generated when a new Account is created. +// noUSB disables USB support that is required to support hardware devices such as +// ledger and trezor. +func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abidb *AbiDb, lightKDF bool) *SignerAPI { + var ( + backends []accounts.Backend + n, p = keystore.StandardScryptN, keystore.StandardScryptP + ) + if lightKDF { + n, p = keystore.LightScryptN, keystore.LightScryptP + } + // support password based accounts + if len(ksLocation) > 0 { + backends = append(backends, keystore.NewKeyStore(ksLocation, n, p)) + } + if !noUSB { + // Start a USB hub for Ledger hardware wallets + if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { + log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) + } else { + backends = append(backends, ledgerhub) + log.Debug("Ledger support enabled") + } + // Start a USB hub for Trezor hardware wallets + if trezorhub, err := usbwallet.NewTrezorHub(); err != nil { + log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err)) + } else { + backends = append(backends, trezorhub) + log.Debug("Trezor support enabled") + } + } + return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, NewValidator(abidb)} +} + +// List returns the set of wallet this signer manages. Each wallet can contain +// multiple accounts. +func (api *SignerAPI) List(ctx context.Context) (Accounts, error) { + var accs []Account + for _, wallet := range api.am.Wallets() { + for _, acc := range wallet.Accounts() { + acc := Account{Typ: "Account", URL: wallet.URL(), Address: acc.Address} + accs = append(accs, acc) + } + } + result, err := api.UI.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)}) + if err != nil { + return nil, err + } + if result.Accounts == nil { + return nil, ErrRequestDenied + + } + return result.Accounts, nil +} + +// New creates a new password protected Account. The private key is protected with +// the given password. Users are responsible to backup the private key that is stored +// in the keystore location thas was specified when this API was created. +func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) { + be := api.am.Backends(keystore.KeyStoreType) + if len(be) == 0 { + return accounts.Account{}, errors.New("password based accounts not supported") + } + resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}) + + if err != nil { + return accounts.Account{}, err + } + if !resp.Approved { + return accounts.Account{}, ErrRequestDenied + } + return be[0].(*keystore.KeyStore).NewAccount(resp.Password) +} + +// logDiff logs the difference between the incoming (original) transaction and the one returned from the signer. +// it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow +// UI-modifications to requests +func logDiff(original *SignTxRequest, new *SignTxResponse) bool { + modified := false + if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) { + log.Info("Sender-account changed by UI", "was", f0, "is", f1) + modified = true + } + if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) { + log.Info("Recipient-account changed by UI", "was", t0, "is", t1) + modified = true + } + if g0, g1 := original.Transaction.Gas, new.Transaction.Gas; g0 != g1 { + modified = true + log.Info("Gas changed by UI", "was", g0, "is", g1) + } + if g0, g1 := big.Int(original.Transaction.GasPrice), big.Int(new.Transaction.GasPrice); g0.Cmp(&g1) != 0 { + modified = true + log.Info("GasPrice changed by UI", "was", g0, "is", g1) + } + if v0, v1 := big.Int(original.Transaction.Value), big.Int(new.Transaction.Value); v0.Cmp(&v1) != 0 { + modified = true + log.Info("Value changed by UI", "was", v0, "is", v1) + } + if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 { + d0s := "" + d1s := "" + if d0 != nil { + d0s = common.ToHex(*d0) + } + if d1 != nil { + d1s = common.ToHex(*d1) + } + if d1s != d0s { + modified = true + log.Info("Data changed by UI", "was", d0s, "is", d1s) + } + } + if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 { + modified = true + log.Info("Nonce changed by UI", "was", n0, "is", n1) + } + return modified +} + +// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form +func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { + var ( + err error + result SignTxResponse + ) + msgs, err := api.validator.ValidateTransaction(&args, methodSelector) + if err != nil { + return nil, err + } + + req := SignTxRequest{ + Transaction: args, + Meta: MetadataFromContext(ctx), + Callinfo: msgs.Messages, + } + // Process approval + result, err = api.UI.ApproveTx(&req) + if err != nil { + return nil, err + } + if !result.Approved { + return nil, ErrRequestDenied + } + // Log changes made by the UI to the signing-request + logDiff(&req, &result) + var ( + acc accounts.Account + wallet accounts.Wallet + ) + acc = accounts.Account{Address: result.Transaction.From.Address()} + wallet, err = api.am.Find(acc) + if err != nil { + return nil, err + } + // Convert fields into a real transaction + var unsignedTx = result.Transaction.toTransaction() + + // The one to sign is the one that was returned from the UI + signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + rlpdata, err := rlp.EncodeToBytes(signedTx) + response := ethapi.SignTransactionResult{Raw: rlpdata, Tx: signedTx} + + // Finally, send the signed tx to the UI + api.UI.OnApprovedTx(response) + // ...and to the external caller + return &response, nil + +} + +// Sign calculates an Ethereum ECDSA signature for: +// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) +// +// Note, the produced signature conforms to the secp256k1 curve R, S and V values, +// where the V value will be 27 or 28 for legacy reasons. +// +// The key used to calculate the signature is decrypted with the given password. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + sighash, msg := SignHash(data) + // We make the request prior to looking up if we actually have the account, to prevent + // account-enumeration via the API + req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)} + res, err := api.UI.ApproveSignData(req) + + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + account := accounts.Account{Address: addr.Address()} + wallet, err := api.am.Find(account) + if err != nil { + return nil, err + } + // Assemble sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil +} + +// EcRecover returns the address for the Account that was used to create the signature. +// Note, this function is compatible with eth_sign and personal_sign. As such it recovers +// the address of: +// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) +// addr = ecrecover(hash, signature) +// +// Note, the signature must conform to the secp256k1 curve R, S and V values, where +// the V value must be be 27 or 28 for legacy reasons. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover +func (api *SignerAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) { + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignHash(data) + rpk, err := crypto.Ecrecover(hash, sig) + if err != nil { + return common.Address{}, err + } + pubKey := crypto.ToECDSAPub(rpk) + recoveredAddr := crypto.PubkeyToAddress(*pubKey) + return recoveredAddr, nil +} + +// SignHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func SignHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// Export returns encrypted private key associated with the given address in web3 keystore format. +func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { + res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) + + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + wallet, err := api.am.Find(accounts.Account{Address: addr}) + if err != nil { + return nil, err + } + if wallet.URL().Scheme != keystore.KeyStoreScheme { + return nil, fmt.Errorf("Account is not a keystore-account") + } + return ioutil.ReadFile(wallet.URL().Path) +} + +// Import tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be +// in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful +// decryption it will encrypt the key with the given newPassphrase and store it in the keystore. +func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) { + be := api.am.Backends(keystore.KeyStoreType) + + if len(be) == 0 { + return Account{}, errors.New("password based accounts not supported") + } + res, err := api.UI.ApproveImport(&ImportRequest{Meta: MetadataFromContext(ctx)}) + + if err != nil { + return Account{}, err + } + if !res.Approved { + return Account{}, ErrRequestDenied + } + acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, res.OldPassword, res.NewPassword) + if err != nil { + api.UI.ShowError(err.Error()) + return Account{}, err + } + return Account{Typ: "Account", URL: acc.URL, Address: acc.Address}, nil +} diff --git a/signer/core/api_test.go b/signer/core/api_test.go new file mode 100644 index 0000000000..50ad021987 --- /dev/null +++ b/signer/core/api_test.go @@ -0,0 +1,386 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +// +package core + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rlp" +) + +//Used for testing +type HeadlessUI struct { + controller chan string +} + +func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) { +} + +func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + fmt.Printf("OnApproved called") +} + +func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { + + switch <-ui.controller { + case "Y": + return SignTxResponse{request.Transaction, true, <-ui.controller}, nil + case "M": //Modify + old := big.Int(request.Transaction.Value) + newVal := big.NewInt(0).Add(&old, big.NewInt(1)) + request.Transaction.Value = hexutil.Big(*newVal) + return SignTxResponse{request.Transaction, true, <-ui.controller}, nil + default: + return SignTxResponse{request.Transaction, false, ""}, nil + } +} +func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { + if "Y" == <-ui.controller { + return SignDataResponse{true, <-ui.controller}, nil + } + return SignDataResponse{false, ""}, nil +} +func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { + + return ExportResponse{<-ui.controller == "Y"}, nil + +} +func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { + + if "Y" == <-ui.controller { + return ImportResponse{true, <-ui.controller, <-ui.controller}, nil + } + return ImportResponse{false, "", ""}, nil +} +func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) { + + switch <-ui.controller { + case "A": + return ListResponse{request.Accounts}, nil + case "1": + l := make([]Account, 1) + l[0] = request.Accounts[1] + return ListResponse{l}, nil + default: + return ListResponse{nil}, nil + } +} +func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { + + if "Y" == <-ui.controller { + return NewAccountResponse{true, <-ui.controller}, nil + } + return NewAccountResponse{false, ""}, nil +} +func (ui *HeadlessUI) ShowError(message string) { + //stdout is used by communication + fmt.Fprint(os.Stderr, message) +} +func (ui *HeadlessUI) ShowInfo(message string) { + //stdout is used by communication + fmt.Fprint(os.Stderr, message) +} + +func tmpDirName(t *testing.T) string { + d, err := ioutil.TempDir("", "eth-keystore-test") + if err != nil { + t.Fatal(err) + } + d, err = filepath.EvalSymlinks(d) + if err != nil { + t.Fatal(err) + } + return d +} + +func setup(t *testing.T) (*SignerAPI, chan string) { + + controller := make(chan string, 10) + + db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json") + if err != nil { + utils.Fatalf(err.Error()) + } + var ( + ui = &HeadlessUI{controller} + api = NewSignerAPI( + 1, + tmpDirName(t), + true, + ui, + db, + true) + ) + return api, controller +} +func createAccount(control chan string, api *SignerAPI, t *testing.T) { + + control <- "Y" + control <- "apassword" + _, err := api.New(context.Background()) + if err != nil { + t.Fatal(err) + } + // Some time to allow changes to propagate + time.Sleep(250 * time.Millisecond) +} +func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) { + control <- "N" + acc, err := api.New(context.Background()) + if err != ErrRequestDenied { + t.Fatal(err) + } + if acc.Address != (common.Address{}) { + t.Fatal("Empty address should be returned") + } +} +func list(control chan string, api *SignerAPI, t *testing.T) []Account { + control <- "A" + list, err := api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + return list +} + +func TestNewAcc(t *testing.T) { + + api, control := setup(t) + verifyNum := func(num int) { + if list := list(control, api, t); len(list) != num { + t.Errorf("Expected %d accounts, got %d", num, len(list)) + } + } + // Testing create and create-deny + createAccount(control, api, t) + createAccount(control, api, t) + failCreateAccount(control, api, t) + failCreateAccount(control, api, t) + createAccount(control, api, t) + failCreateAccount(control, api, t) + createAccount(control, api, t) + failCreateAccount(control, api, t) + verifyNum(4) + + // Testing listing: + // Listing one Account + control <- "1" + list, err := api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + if len(list) != 1 { + t.Fatalf("List should only show one Account") + } + // Listing denied + control <- "Nope" + list, err = api.List(context.Background()) + if len(list) != 0 { + t.Fatalf("List should be empty") + } + if err != ErrRequestDenied { + t.Fatal("Expected deny") + } +} + +func TestSignData(t *testing.T) { + + api, control := setup(t) + //Create two accounts + createAccount(control, api, t) + createAccount(control, api, t) + control <- "1" + list, err := api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + a := common.NewMixedcaseAddress(list[0].Address) + + control <- "Y" + control <- "wrongpassword" + h, err := api.Sign(context.Background(), a, []byte("EHLO world")) + if h != nil { + t.Errorf("Expected nil-data, got %x", h) + } + if err != keystore.ErrDecrypt { + t.Errorf("Expected ErrLocked! %v", err) + } + + control <- "No way" + h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + if h != nil { + t.Errorf("Expected nil-data, got %x", h) + } + if err != ErrRequestDenied { + t.Errorf("Expected ErrRequestDenied! %v", err) + } + + control <- "Y" + control <- "apassword" + h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + + if err != nil { + t.Fatal(err) + } + if h == nil || len(h) != 65 { + t.Errorf("Expected 65 byte signature (got %d bytes)", len(h)) + } +} +func mkTestTx(from common.MixedcaseAddress) SendTxArgs { + to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) + gas := hexutil.Uint64(21000) + gasPrice := (hexutil.Big)(*big.NewInt(2000000000)) + value := (hexutil.Big)(*big.NewInt(1e18)) + nonce := (hexutil.Uint64)(0) + data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) + tx := SendTxArgs{ + From: from, + To: &to, + Gas: gas, + GasPrice: gasPrice, + Value: value, + Data: &data, + Nonce: nonce} + return tx +} + +func TestSignTx(t *testing.T) { + + var ( + list Accounts + res, res2 *ethapi.SignTransactionResult + err error + ) + + api, control := setup(t) + createAccount(control, api, t) + control <- "A" + list, err = api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + a := common.NewMixedcaseAddress(list[0].Address) + + methodSig := "test(uint)" + tx := mkTestTx(a) + + control <- "Y" + control <- "wrongpassword" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) + if res != nil { + t.Errorf("Expected nil-response, got %v", res) + } + if err != keystore.ErrDecrypt { + t.Errorf("Expected ErrLocked! %v", err) + } + + control <- "No way" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) + if res != nil { + t.Errorf("Expected nil-response, got %v", res) + } + if err != ErrRequestDenied { + t.Errorf("Expected ErrRequestDenied! %v", err) + } + + control <- "Y" + control <- "apassword" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) + + if err != nil { + t.Fatal(err) + } + parsedTx := &types.Transaction{} + rlp.Decode(bytes.NewReader(res.Raw), parsedTx) + //The tx should NOT be modified by the UI + if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { + t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) + } + control <- "Y" + control <- "apassword" + + res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(res.Raw, res2.Raw) { + t.Error("Expected tx to be unmodified by UI") + } + + //The tx is modified by the UI + control <- "M" + control <- "apassword" + + res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + if err != nil { + t.Fatal(err) + } + + parsedTx2 := &types.Transaction{} + rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) + //The tx should be modified by the UI + if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { + t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) + } + + if bytes.Equal(res.Raw, res2.Raw) { + t.Error("Expected tx to be modified by UI") + } + +} + +/* +func TestAsyncronousResponses(t *testing.T){ + + //Set up one account + api, control := setup(t) + createAccount(control, api, t) + + // Two transactions, the second one with larger value than the first + tx1 := mkTestTx() + newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1)) + tx2 := mkTestTx() + tx2.Value = (*hexutil.Big)(newVal) + + control <- "W" //wait + control <- "Y" // + control <- "apassword" + control <- "Y" // + control <- "apassword" + + var err error + + h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil) + h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil) + + + } +*/ diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go new file mode 100644 index 0000000000..d0ba733d2f --- /dev/null +++ b/signer/core/auditlog.go @@ -0,0 +1,110 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "context" + + "encoding/json" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" +) + +type AuditLogger struct { + log log.Logger + api ExternalAPI +} + +func (l *AuditLogger) List(ctx context.Context) (Accounts, error) { + l.log.Info("List", "type", "request", "metadata", MetadataFromContext(ctx).String()) + res, e := l.api.List(ctx) + + l.log.Info("List", "type", "response", "data", res.String()) + + return res, e +} + +func (l *AuditLogger) New(ctx context.Context) (accounts.Account, error) { + return l.api.New(ctx) +} + +func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { + sel := "" + if methodSelector != nil { + sel = *methodSelector + } + l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "tx", args.String(), + "methodSelector", sel) + + res, e := l.api.SignTransaction(ctx, args, methodSelector) + if res != nil { + l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(res.Raw), "error", e) + } else { + l.log.Info("SignTransaction", "type", "response", "data", res, "error", e) + } + return res, e +} + +func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", common.Bytes2Hex(data)) + b, e := l.api.Sign(ctx, addr, data) + l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e) + return b, e +} + +func (l *AuditLogger) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) { + l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "data", common.Bytes2Hex(data)) + a, e := l.api.EcRecover(ctx, data, sig) + l.log.Info("EcRecover", "type", "response", "addr", a.String(), "error", e) + return a, e +} + +func (l *AuditLogger) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { + l.log.Info("Export", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.Hex()) + j, e := l.api.Export(ctx, addr) + // In this case, we don't actually log the json-response, which may be extra sensitive + l.log.Info("Export", "type", "response", "json response size", len(j), "error", e) + return j, e +} + +func (l *AuditLogger) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) { + // Don't actually log the json contents + l.log.Info("Import", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "keyJSON size", len(keyJSON)) + a, e := l.api.Import(ctx, keyJSON) + l.log.Info("Import", "type", "response", "addr", a.String(), "error", e) + return a, e +} + +func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) { + l := log.New("api", "signer") + handler, err := log.FileHandler(path, log.LogfmtFormat()) + if err != nil { + return nil, err + } + l.SetHandler(handler) + l.Info("Configured", "audit log", path) + return &AuditLogger{l, api}, nil +} diff --git a/signer/core/cliui.go b/signer/core/cliui.go new file mode 100644 index 0000000000..2f969669c2 --- /dev/null +++ b/signer/core/cliui.go @@ -0,0 +1,248 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "bufio" + "fmt" + "os" + "strings" + + "sync" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/crypto/ssh/terminal" +) + +type CommandlineUI struct { + in *bufio.Reader + mu sync.Mutex +} + +func NewCommandlineUI() *CommandlineUI { + return &CommandlineUI{in: bufio.NewReader(os.Stdin)} +} + +// readString reads a single line from stdin, trimming if from spaces, enforcing +// non-emptyness. +func (ui *CommandlineUI) readString() string { + for { + fmt.Printf("> ") + text, err := ui.in.ReadString('\n') + if err != nil { + log.Crit("Failed to read user input", "err", err) + } + if text = strings.TrimSpace(text); text != "" { + return text + } + } +} + +// readPassword reads a single line from stdin, trimming it from the trailing new +// line and returns it. The input will not be echoed. +func (ui *CommandlineUI) readPassword() string { + fmt.Printf("Enter password to approve:\n") + fmt.Printf("> ") + + text, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + log.Crit("Failed to read password", "err", err) + } + fmt.Println() + fmt.Println("-----------------------") + return string(text) +} + +// readPassword reads a single line from stdin, trimming it from the trailing new +// line and returns it. The input will not be echoed. +func (ui *CommandlineUI) readPasswordText(inputstring string) string { + fmt.Printf("Enter %s:\n", inputstring) + fmt.Printf("> ") + text, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + log.Crit("Failed to read password", "err", err) + } + fmt.Println("-----------------------") + return string(text) +} + +// confirm returns true if user enters 'Yes', otherwise false +func (ui *CommandlineUI) confirm() bool { + fmt.Printf("Approve? [y/N]:\n") + if ui.readString() == "y" { + return true + } + fmt.Println("-----------------------") + return false +} + +func showMetadata(metadata Metadata) { + fmt.Printf("Request context:\n\t%v -> %v -> %v\n", metadata.Remote, metadata.Scheme, metadata.Local) +} + +// ApproveTx prompt the user for confirmation to request to sign Transaction +func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + weival := request.Transaction.Value.ToInt() + fmt.Printf("--------- Transaction request-------------\n") + if to := request.Transaction.To; to != nil { + fmt.Printf("to: %v\n", to.Original()) + if !to.ValidChecksum() { + fmt.Printf("\nWARNING: Invalid checksum on to-address!\n\n") + } + } else { + fmt.Printf("to: \n") + } + fmt.Printf("from: %v\n", request.Transaction.From.String()) + fmt.Printf("value: %v wei\n", weival) + if request.Transaction.Data != nil { + d := *request.Transaction.Data + if len(d) > 0 { + fmt.Printf("data: %v\n", common.Bytes2Hex(d)) + } + } + if request.Callinfo != nil { + fmt.Printf("\nTransaction validation:\n") + for _, m := range request.Callinfo { + fmt.Printf(" * %s : %s", m.Typ, m.Message) + } + fmt.Println() + + } + fmt.Printf("\n") + showMetadata(request.Meta) + fmt.Printf("-------------------------------------------\n") + if !ui.confirm() { + return SignTxResponse{request.Transaction, false, ""}, nil + } + return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil +} + +// ApproveSignData prompt the user for confirmation to request to sign data +func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- Sign data request--------------\n") + fmt.Printf("Account: %s\n", request.Address.String()) + fmt.Printf("message: \n%q\n", request.Message) + fmt.Printf("raw data: \n%v\n", request.Rawdata) + fmt.Printf("message hash: %v\n", request.Hash) + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + if !ui.confirm() { + return SignDataResponse{false, ""}, nil + } + return SignDataResponse{true, ui.readPassword()}, nil +} + +// ApproveExport prompt the user for confirmation to export encrypted Account json +func (ui *CommandlineUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- Export Account request--------------\n") + fmt.Printf("A request has been made to export the (encrypted) keyfile\n") + fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n") + fmt.Printf("\n") + fmt.Printf("Account: %x\n", request.Address) + //fmt.Printf("keyfile: \n%v\n", request.file) + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + return ExportResponse{ui.confirm()}, nil +} + +// ApproveImport prompt the user for confirmation to import Account json +func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- Import Account request--------------\n") + fmt.Printf("A request has been made to import an encrypted keyfile\n") + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + if !ui.confirm() { + return ImportResponse{false, "", ""}, nil + } + return ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}, nil +} + +// ApproveListing prompt the user for confirmation to list accounts +// the list of accounts to list can be modified by the UI +func (ui *CommandlineUI) ApproveListing(request *ListRequest) (ListResponse, error) { + + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- List Account request--------------\n") + fmt.Printf("A request has been made to list all accounts. \n") + fmt.Printf("You can select which accounts the caller can see\n") + for _, account := range request.Accounts { + fmt.Printf("\t[x] %v\n", account.Address.Hex()) + } + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + if !ui.confirm() { + return ListResponse{nil}, nil + } + return ListResponse{request.Accounts}, nil +} + +// ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller +func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { + + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- New Account request--------------\n") + fmt.Printf("A request has been made to create a new. \n") + fmt.Printf("Approving this operation means that a new Account is created,\n") + fmt.Printf("and the address show to the caller\n") + showMetadata(request.Meta) + if !ui.confirm() { + return NewAccountResponse{false, ""}, nil + } + return NewAccountResponse{true, ui.readPassword()}, nil +} + +// ShowError displays error message to user +func (ui *CommandlineUI) ShowError(message string) { + + fmt.Printf("ERROR: %v\n", message) +} + +// ShowInfo displays info message to user +func (ui *CommandlineUI) ShowInfo(message string) { + fmt.Printf("Info: %v\n", message) +} + +func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + fmt.Printf("Transaction signed:\n ") + spew.Dump(tx.Tx) +} + +func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) { + + fmt.Printf("------- Signer info -------\n") + for k, v := range info.Info { + fmt.Printf("* %v : %v\n", k, v) + } +} diff --git a/signer/core/stdioui.go b/signer/core/stdioui.go new file mode 100644 index 0000000000..5640ed03bd --- /dev/null +++ b/signer/core/stdioui.go @@ -0,0 +1,113 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +// + +package core + +import ( + "context" + "sync" + + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +type StdIOUI struct { + client rpc.Client + mu sync.Mutex +} + +func NewStdIOUI() *StdIOUI { + log.Info("NewStdIOUI") + client, err := rpc.DialContext(context.Background(), "stdio://") + if err != nil { + log.Crit("Could not create stdio client", "err", err) + } + return &StdIOUI{client: *client} +} + +// dispatch sends a request over the stdio +func (ui *StdIOUI) dispatch(serviceMethod string, args interface{}, reply interface{}) error { + err := ui.client.Call(&reply, serviceMethod, args) + if err != nil { + log.Info("Error", "exc", err.Error()) + } + return err +} + +func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { + var result SignTxResponse + err := ui.dispatch("ApproveTx", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { + var result SignDataResponse + err := ui.dispatch("ApproveSignData", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { + var result ExportResponse + err := ui.dispatch("ApproveExport", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { + var result ImportResponse + err := ui.dispatch("ApproveImport", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) { + var result ListResponse + err := ui.dispatch("ApproveListing", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { + var result NewAccountResponse + err := ui.dispatch("ApproveNewAccount", request, &result) + return result, err +} + +func (ui *StdIOUI) ShowError(message string) { + err := ui.dispatch("ShowError", &Message{message}, nil) + if err != nil { + log.Info("Error calling 'ShowError'", "exc", err.Error(), "msg", message) + } +} + +func (ui *StdIOUI) ShowInfo(message string) { + err := ui.dispatch("ShowInfo", Message{message}, nil) + if err != nil { + log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message) + } +} +func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + err := ui.dispatch("OnApprovedTx", tx, nil) + if err != nil { + log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx) + } +} + +func (ui *StdIOUI) OnSignerStartup(info StartupInfo) { + err := ui.dispatch("OnSignerStartup", info, nil) + if err != nil { + log.Info("Error calling 'OnSignerStartup'", "exc", err.Error(), "info", info) + } +} diff --git a/signer/core/types.go b/signer/core/types.go new file mode 100644 index 0000000000..2acc0a4f4a --- /dev/null +++ b/signer/core/types.go @@ -0,0 +1,95 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "encoding/json" + "strings" + + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +type Accounts []Account + +func (as Accounts) String() string { + var output []string + for _, a := range as { + output = append(output, a.String()) + } + return strings.Join(output, "\n") +} + +type Account struct { + Typ string `json:"type"` + URL accounts.URL `json:"url"` + Address common.Address `json:"address"` +} + +func (a Account) String() string { + s, err := json.Marshal(a) + if err == nil { + return string(s) + } + return err.Error() +} + +type ValidationInfo struct { + Typ string `json:"type"` + Message string `json:"message"` +} +type ValidationMessages struct { + Messages []ValidationInfo +} + +// SendTxArgs represents the arguments to submit a transaction +type SendTxArgs struct { + From common.MixedcaseAddress `json:"from"` + To *common.MixedcaseAddress `json:"to"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice hexutil.Big `json:"gasPrice"` + Value hexutil.Big `json:"value"` + Nonce hexutil.Uint64 `json:"nonce"` + // We accept "data" and "input" for backwards-compatibility reasons. + Data *hexutil.Bytes `json:"data"` + Input *hexutil.Bytes `json:"input"` +} + +func (args SendTxArgs) String() string { + s, err := json.Marshal(args) + if err == nil { + return string(s) + } + return err.Error() +} + +func (args *SendTxArgs) toTransaction() *types.Transaction { + var input []byte + if args.Data != nil { + input = *args.Data + } else if args.Input != nil { + input = *args.Input + } + if args.To == nil { + return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input) + } + return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input) +} diff --git a/signer/core/validation.go b/signer/core/validation.go new file mode 100644 index 0000000000..288456df87 --- /dev/null +++ b/signer/core/validation.go @@ -0,0 +1,163 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// The validation package contains validation checks for transactions +// - ABI-data validation +// - Transaction semantics validation +// The package provides warnings for typical pitfalls + +func (vs *ValidationMessages) crit(msg string) { + vs.Messages = append(vs.Messages, ValidationInfo{"CRITICAL", msg}) +} +func (vs *ValidationMessages) warn(msg string) { + vs.Messages = append(vs.Messages, ValidationInfo{"WARNING", msg}) +} +func (vs *ValidationMessages) info(msg string) { + vs.Messages = append(vs.Messages, ValidationInfo{"Info", msg}) +} + +type Validator struct { + db *AbiDb +} + +func NewValidator(db *AbiDb) *Validator { + return &Validator{db} +} +func testSelector(selector string, data []byte) (*decodedCallData, error) { + if selector == "" { + return nil, fmt.Errorf("selector not found") + } + abiData, err := MethodSelectorToAbi(selector) + if err != nil { + return nil, err + } + info, err := parseCallData(data, string(abiData)) + if err != nil { + return nil, err + } + return info, nil + +} + +// validateCallData checks if the ABI-data + methodselector (if given) can be parsed and seems to match +func (v *Validator) validateCallData(msgs *ValidationMessages, data []byte, methodSelector *string) { + if len(data) == 0 { + return + } + if len(data) < 4 { + msgs.warn("Tx contains data which is not valid ABI") + return + } + var ( + info *decodedCallData + err error + ) + // Check the provided one + if methodSelector != nil { + info, err = testSelector(*methodSelector, data) + if err != nil { + msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err)) + } else { + msgs.info(info.String()) + //Successfull match. add to db if not there already (ignore errors there) + v.db.AddSignature(*methodSelector, data[:4]) + } + return + } + // Check the db + selector, err := v.db.LookupMethodSelector(data[:4]) + if err != nil { + msgs.warn(fmt.Sprintf("Tx contains data, but the ABI signature could not be found: %v", err)) + return + } + info, err = testSelector(selector, data) + if err != nil { + msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err)) + } else { + msgs.info(info.String()) + } +} + +// validateSemantics checks if the transactions 'makes sense', and generate warnings for a couple of typical scenarios +func (v *Validator) validate(msgs *ValidationMessages, txargs *SendTxArgs, methodSelector *string) error { + // Prevent accidental erroneous usage of both 'input' and 'data' + if txargs.Data != nil && txargs.Input != nil && !bytes.Equal(*txargs.Data, *txargs.Input) { + // This is a showstopper + return errors.New(`Ambiguous request: both "data" and "input" are set and are not identical`) + } + var ( + data []byte + ) + // Place data on 'data', and nil 'input' + if txargs.Input != nil { + txargs.Data = txargs.Input + txargs.Input = nil + } + if txargs.Data != nil { + data = *txargs.Data + } + + if txargs.To == nil { + //Contract creation should contain sufficient data to deploy a contract + // A typical error is omitting sender due to some quirk in the javascript call + // e.g. https://github.com/ethereum/go-ethereum/issues/16106 + if len(data) == 0 { + if txargs.Value.ToInt().Cmp(big.NewInt(0)) > 0 { + // Sending ether into black hole + return errors.New("Tx will create contract with value but empty code!") + } + // No value submitted at least + msgs.crit("Tx will create contract with empty code!") + } else if len(data) < 40 { //Arbitrary limit + msgs.warn(fmt.Sprintf("Tx will will create contract, but payload is suspiciously small (%d b)", len(data))) + } + // methodSelector should be nil for contract creation + if methodSelector != nil { + msgs.warn("Tx will create contract, but method selector supplied; indicating intent to call a method.") + } + + } else { + if !txargs.To.ValidChecksum() { + msgs.warn("Invalid checksum on to-address") + } + // Normal transaction + if bytes.Equal(txargs.To.Address().Bytes(), common.Address{}.Bytes()) { + // Sending to 0 + msgs.crit("Tx destination is the zero address!") + } + // Validate calldata + v.validateCallData(msgs, data, methodSelector) + } + return nil +} + +// ValidateTransaction does a number of checks on the supplied transaction, and returns either a list of warnings, +// or an error, indicating that the transaction should be immediately rejected +func (v *Validator) ValidateTransaction(txArgs *SendTxArgs, methodSelector *string) (*ValidationMessages, error) { + msgs := &ValidationMessages{} + return msgs, v.validate(msgs, txArgs, methodSelector) +} diff --git a/signer/core/validation_test.go b/signer/core/validation_test.go new file mode 100644 index 0000000000..2b33a8630b --- /dev/null +++ b/signer/core/validation_test.go @@ -0,0 +1,139 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package core + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func hexAddr(a string) common.Address { return common.BytesToAddress(common.FromHex(a)) } +func mixAddr(a string) (*common.MixedcaseAddress, error) { + return common.NewMixedcaseAddressFromString(a) +} +func toHexBig(h string) hexutil.Big { + b := big.NewInt(0).SetBytes(common.FromHex(h)) + return hexutil.Big(*b) +} +func toHexUint(h string) hexutil.Uint64 { + b := big.NewInt(0).SetBytes(common.FromHex(h)) + return hexutil.Uint64(b.Uint64()) +} +func dummyTxArgs(t txtestcase) *SendTxArgs { + to, _ := mixAddr(t.to) + from, _ := mixAddr(t.from) + n := toHexUint(t.n) + gas := toHexUint(t.g) + gasPrice := toHexBig(t.gp) + value := toHexBig(t.value) + var ( + data, input *hexutil.Bytes + ) + if t.d != "" { + a := hexutil.Bytes(common.FromHex(t.d)) + data = &a + } + if t.i != "" { + a := hexutil.Bytes(common.FromHex(t.i)) + input = &a + + } + return &SendTxArgs{ + From: *from, + To: to, + Value: value, + Nonce: n, + GasPrice: gasPrice, + Gas: gas, + Data: data, + Input: input, + } +} + +type txtestcase struct { + from, to, n, g, gp, value, d, i string + expectErr bool + numMessages int +} + +func TestValidator(t *testing.T) { + var ( + // use empty db, there are other tests for the abi-specific stuff + db, _ = NewEmptyAbiDB() + v = NewValidator(db) + ) + testcases := []txtestcase{ + // Invalid to checksum + {from: "000000000000000000000000000000000000dead", to: "000000000000000000000000000000000000dead", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1}, + // valid 0x000000000000000000000000000000000000dEaD + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 0}, + // conflicting input and data + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", i: "0x02", expectErr: true}, + // Data can't be parsed + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x0102", numMessages: 1}, + // Data (on Input) can't be parsed + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", i: "0x0102", numMessages: 1}, + // Send to 0 + {from: "000000000000000000000000000000000000dead", to: "0x0000000000000000000000000000000000000000", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1}, + // Create empty contract (no value) + {from: "000000000000000000000000000000000000dead", to: "", + n: "0x01", g: "0x20", gp: "0x40", value: "0x00", numMessages: 1}, + // Create empty contract (with value) + {from: "000000000000000000000000000000000000dead", to: "", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", expectErr: true}, + // Small payload for create + {from: "000000000000000000000000000000000000dead", to: "", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", numMessages: 1}, + } + for i, test := range testcases { + msgs, err := v.ValidateTransaction(dummyTxArgs(test), nil) + if err == nil && test.expectErr { + t.Errorf("Test %d, expected error", i) + for _, msg := range msgs.Messages { + fmt.Printf("* %s: %s\n", msg.Typ, msg.Message) + } + } + if err != nil && !test.expectErr { + t.Errorf("Test %d, unexpected error: %v", i, err) + } + if err == nil { + got := len(msgs.Messages) + if got != test.numMessages { + for _, msg := range msgs.Messages { + fmt.Printf("* %s: %s\n", msg.Typ, msg.Message) + } + t.Errorf("Test %d, expected %d messages, got %d", i, test.numMessages, got) + } else { + //Debug printout, remove later + for _, msg := range msgs.Messages { + fmt.Printf("* [%d] %s: %s\n", i, msg.Typ, msg.Message) + } + fmt.Println() + } + } + } +} diff --git a/signer/rules/deps/bignumber.js b/signer/rules/deps/bignumber.js new file mode 100644 index 0000000000..17c8851e24 --- /dev/null +++ b/signer/rules/deps/bignumber.js @@ -0,0 +1,4 @@ +/* bignumber.js v2.0.3 https://github.com/MikeMcl/bignumber.js/LICENCE */ +/* modified by zelig to fix https://github.com/robertkrimen/otto#regular-expression-incompatibility */ +!function(e){"use strict";function n(e){function a(e,n){var t,r,i,o,u,s,f=this;if(!(f instanceof a))return j&&L(26,"constructor call without new",e),new a(e,n);if(null!=n&&H(n,2,64,M,"base")){if(n=0|n,s=e+"",10==n)return f=new a(e instanceof a?e:s),U(f,P+f.e+1,k);if((o="number"==typeof e)&&0*e!=0||!new RegExp("^-?"+(t="["+O.slice(0,n)+"]+")+"(?:\\."+t+")?$",37>n?"i":"").test(s))return g(f,s,o,n);o?(f.s=0>1/e?(s=s.slice(1),-1):1,j&&s.replace(/^0\.0*|\./,"").length>15&&L(M,b,e),o=!1):f.s=45===s.charCodeAt(0)?(s=s.slice(1),-1):1,s=D(s,10,n,f.s)}else{if(e instanceof a)return f.s=e.s,f.e=e.e,f.c=(e=e.c)?e.slice():e,void(M=0);if((o="number"==typeof e)&&0*e==0){if(f.s=0>1/e?(e=-e,-1):1,e===~~e){for(r=0,i=e;i>=10;i/=10,r++);return f.e=r,f.c=[e],void(M=0)}s=e+""}else{if(!p.test(s=e+""))return g(f,s,o);f.s=45===s.charCodeAt(0)?(s=s.slice(1),-1):1}}for((r=s.indexOf("."))>-1&&(s=s.replace(".","")),(i=s.search(/e/i))>0?(0>r&&(r=i),r+=+s.slice(i+1),s=s.substring(0,i)):0>r&&(r=s.length),i=0;48===s.charCodeAt(i);i++);for(u=s.length;48===s.charCodeAt(--u););if(s=s.slice(i,u+1))if(u=s.length,o&&j&&u>15&&L(M,b,f.s*e),r=r-i-1,r>z)f.c=f.e=null;else if(G>r)f.c=[f.e=0];else{if(f.e=r,f.c=[],i=(r+1)%y,0>r&&(i+=y),u>i){for(i&&f.c.push(+s.slice(0,i)),u-=y;u>i;)f.c.push(+s.slice(i,i+=y));s=s.slice(i),i=y-s.length}else i-=u;for(;i--;s+="0");f.c.push(+s)}else f.c=[f.e=0];M=0}function D(e,n,t,i){var o,u,f,c,h,g,p,d=e.indexOf("."),m=P,w=k;for(37>t&&(e=e.toLowerCase()),d>=0&&(f=J,J=0,e=e.replace(".",""),p=new a(t),h=p.pow(e.length-d),J=f,p.c=s(l(r(h.c),h.e),10,n),p.e=p.c.length),g=s(e,t,n),u=f=g.length;0==g[--f];g.pop());if(!g[0])return"0";if(0>d?--u:(h.c=g,h.e=u,h.s=i,h=C(h,p,m,w,n),g=h.c,c=h.r,u=h.e),o=u+m+1,d=g[o],f=n/2,c=c||0>o||null!=g[o+1],c=4>w?(null!=d||c)&&(0==w||w==(h.s<0?3:2)):d>f||d==f&&(4==w||c||6==w&&1&g[o-1]||w==(h.s<0?8:7)),1>o||!g[0])e=c?l("1",-m):"0";else{if(g.length=o,c)for(--n;++g[--o]>n;)g[o]=0,o||(++u,g.unshift(1));for(f=g.length;!g[--f];);for(d=0,e="";f>=d;e+=O.charAt(g[d++]));e=l(e,u)}return e}function _(e,n,t,i){var o,u,s,c,h;if(t=null!=t&&H(t,0,8,i,v)?0|t:k,!e.c)return e.toString();if(o=e.c[0],s=e.e,null==n)h=r(e.c),h=19==i||24==i&&B>=s?f(h,s):l(h,s);else if(e=U(new a(e),n,t),u=e.e,h=r(e.c),c=h.length,19==i||24==i&&(u>=n||B>=u)){for(;n>c;h+="0",c++);h=f(h,u)}else if(n-=s,h=l(h,u),u+1>c){if(--n>0)for(h+=".";n--;h+="0");}else if(n+=u-c,n>0)for(u+1==c&&(h+=".");n--;h+="0");return e.s<0&&o?"-"+h:h}function x(e,n){var t,r,i=0;for(u(e[0])&&(e=e[0]),t=new a(e[0]);++ie||e>t||e!=c(e))&&L(r,(i||"decimal places")+(n>e||e>t?" out of range":" not an integer"),e),!0}function I(e,n,t){for(var r=1,i=n.length;!n[--i];n.pop());for(i=n[0];i>=10;i/=10,r++);return(t=r+t*y-1)>z?e.c=e.e=null:G>t?e.c=[e.e=0]:(e.e=t,e.c=n),e}function L(e,n,t){var r=new Error(["new BigNumber","cmp","config","div","divToInt","eq","gt","gte","lt","lte","minus","mod","plus","precision","random","round","shift","times","toDigits","toExponential","toFixed","toFormat","toFraction","pow","toPrecision","toString","BigNumber"][e]+"() "+n+": "+t);throw r.name="BigNumber Error",M=0,r}function U(e,n,t,r){var i,o,u,s,f,l,c,a=e.c,h=R;if(a){e:{for(i=1,s=a[0];s>=10;s/=10,i++);if(o=n-i,0>o)o+=y,u=n,f=a[l=0],c=f/h[i-u-1]%10|0;else if(l=d((o+1)/y),l>=a.length){if(!r)break e;for(;a.length<=l;a.push(0));f=c=0,i=1,o%=y,u=o-y+1}else{for(f=s=a[l],i=1;s>=10;s/=10,i++);o%=y,u=o-y+i,c=0>u?0:f/h[i-u-1]%10|0}if(r=r||0>n||null!=a[l+1]||(0>u?f:f%h[i-u-1]),r=4>t?(c||r)&&(0==t||t==(e.s<0?3:2)):c>5||5==c&&(4==t||r||6==t&&(o>0?u>0?f/h[i-u]:0:a[l-1])%10&1||t==(e.s<0?8:7)),1>n||!a[0])return a.length=0,r?(n-=e.e+1,a[0]=h[n%y],e.e=-n||0):a[0]=e.e=0,e;if(0==o?(a.length=l,s=1,l--):(a.length=l+1,s=h[y-o],a[l]=u>0?m(f/h[i-u]%h[u])*s:0),r)for(;;){if(0==l){for(o=1,u=a[0];u>=10;u/=10,o++);for(u=a[0]+=s,s=1;u>=10;u/=10,s++);o!=s&&(e.e++,a[0]==N&&(a[0]=1));break}if(a[l]+=s,a[l]!=N)break;a[l--]=0,s=1}for(o=a.length;0===a[--o];a.pop());}e.e>z?e.c=e.e=null:e.et?null!=(e=i[t++]):void 0};return f(n="DECIMAL_PLACES")&&H(e,0,E,2,n)&&(P=0|e),r[n]=P,f(n="ROUNDING_MODE")&&H(e,0,8,2,n)&&(k=0|e),r[n]=k,f(n="EXPONENTIAL_AT")&&(u(e)?H(e[0],-E,0,2,n)&&H(e[1],0,E,2,n)&&(B=0|e[0],$=0|e[1]):H(e,-E,E,2,n)&&(B=-($=0|(0>e?-e:e)))),r[n]=[B,$],f(n="RANGE")&&(u(e)?H(e[0],-E,-1,2,n)&&H(e[1],1,E,2,n)&&(G=0|e[0],z=0|e[1]):H(e,-E,E,2,n)&&(0|e?G=-(z=0|(0>e?-e:e)):j&&L(2,n+" cannot be zero",e))),r[n]=[G,z],f(n="ERRORS")&&(e===!!e||1===e||0===e?(M=0,H=(j=!!e)?F:o):j&&L(2,n+w,e)),r[n]=j,f(n="CRYPTO")&&(e===!!e||1===e||0===e?(V=!(!e||!h||"object"!=typeof h),e&&!V&&j&&L(2,"crypto unavailable",h)):j&&L(2,n+w,e)),r[n]=V,f(n="MODULO_MODE")&&H(e,0,9,2,n)&&(W=0|e),r[n]=W,f(n="POW_PRECISION")&&H(e,0,E,2,n)&&(J=0|e),r[n]=J,f(n="FORMAT")&&("object"==typeof e?X=e:j&&L(2,n+" not an object",e)),r[n]=X,r},a.max=function(){return x(arguments,T.lt)},a.min=function(){return x(arguments,T.gt)},a.random=function(){var e=9007199254740992,n=Math.random()*e&2097151?function(){return m(Math.random()*e)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)};return function(e){var t,r,i,o,u,s=0,f=[],l=new a(q);if(e=null!=e&&H(e,0,E,14)?0|e:P,o=d(e/y),V)if(h&&h.getRandomValues){for(t=h.getRandomValues(new Uint32Array(o*=2));o>s;)u=131072*t[s]+(t[s+1]>>>11),u>=9e15?(r=h.getRandomValues(new Uint32Array(2)),t[s]=r[0],t[s+1]=r[1]):(f.push(u%1e14),s+=2);s=o/2}else if(h&&h.randomBytes){for(t=h.randomBytes(o*=7);o>s;)u=281474976710656*(31&t[s])+1099511627776*t[s+1]+4294967296*t[s+2]+16777216*t[s+3]+(t[s+4]<<16)+(t[s+5]<<8)+t[s+6],u>=9e15?h.randomBytes(7).copy(t,s):(f.push(u%1e14),s+=7);s=o/7}else j&&L(14,"crypto unavailable",h);if(!s)for(;o>s;)u=n(),9e15>u&&(f[s++]=u%1e14);for(o=f[--s],e%=y,o&&e&&(u=R[y-e],f[s]=m(o/u)*u);0===f[s];f.pop(),s--);if(0>s)f=[i=0];else{for(i=-1;0===f[0];f.shift(),i-=y);for(s=1,u=f[0];u>=10;u/=10,s++);y>s&&(i-=y-s)}return l.e=i,l.c=f,l}}(),C=function(){function e(e,n,t){var r,i,o,u,s=0,f=e.length,l=n%A,c=n/A|0;for(e=e.slice();f--;)o=e[f]%A,u=e[f]/A|0,r=c*o+u*l,i=l*o+r%A*A+s,s=(i/t|0)+(r/A|0)+c*u,e[f]=i%t;return s&&e.unshift(s),e}function n(e,n,t,r){var i,o;if(t!=r)o=t>r?1:-1;else for(i=o=0;t>i;i++)if(e[i]!=n[i]){o=e[i]>n[i]?1:-1;break}return o}function r(e,n,t,r){for(var i=0;t--;)e[t]-=i,i=e[t]1;e.shift());}return function(i,o,u,s,f){var l,c,h,g,p,d,w,v,b,O,S,R,A,E,D,_,x,F=i.s==o.s?1:-1,I=i.c,L=o.c;if(!(I&&I[0]&&L&&L[0]))return new a(i.s&&o.s&&(I?!L||I[0]!=L[0]:L)?I&&0==I[0]||!L?0*F:F/0:0/0);for(v=new a(F),b=v.c=[],c=i.e-o.e,F=u+c+1,f||(f=N,c=t(i.e/y)-t(o.e/y),F=F/y|0),h=0;L[h]==(I[h]||0);h++);if(L[h]>(I[h]||0)&&c--,0>F)b.push(1),g=!0;else{for(E=I.length,_=L.length,h=0,F+=2,p=m(f/(L[0]+1)),p>1&&(L=e(L,p,f),I=e(I,p,f),_=L.length,E=I.length),A=_,O=I.slice(0,_),S=O.length;_>S;O[S++]=0);x=L.slice(),x.unshift(0),D=L[0],L[1]>=f/2&&D++;do p=0,l=n(L,O,_,S),0>l?(R=O[0],_!=S&&(R=R*f+(O[1]||0)),p=m(R/D),p>1?(p>=f&&(p=f-1),d=e(L,p,f),w=d.length,S=O.length,l=n(d,O,w,S),1==l&&(p--,r(d,w>_?x:L,w,f))):(0==p&&(l=p=1),d=L.slice()),w=d.length,S>w&&d.unshift(0),r(O,d,S,f),-1==l&&(S=O.length,l=n(L,O,_,S),1>l&&(p++,r(O,S>_?x:L,S,f))),S=O.length):0===l&&(p++,O=[0]),b[h++]=p,l&&O[0]?O[S++]=I[A]||0:(O=[I[A]],S=1);while((A++=10;F/=10,h++);U(v,u+(v.e=h+c*y-1)+1,s,g)}else v.e=c,v.r=+g;return v}}(),g=function(){var e=/^(-?)0([xbo])(\w[\w.]*$)/i,n=/^([^.]+)\.$/,t=/^\.([^.]+)$/,r=/^-?(Infinity|NaN)$/,i=/^\s*\+([\w.])|^\s+|\s+$/g;return function(o,u,s,f){var l,c=s?u:u.replace(i,"$1");if(r.test(c))o.s=isNaN(c)?null:0>c?-1:1;else{if(!s&&(c=c.replace(e,function(e,n,t){return l="x"==(t=t.toLowerCase())?16:"b"==t?2:8,f&&f!=l?e:n}),f&&(l=f,c=c.replace(n,"$1").replace(t,"0.$1")),u!=c))return new a(c,l);j&&L(M,"not a"+(f?" base "+f:"")+" number",u),o.s=null}o.c=o.e=null,M=0}}(),T.absoluteValue=T.abs=function(){var e=new a(this);return e.s<0&&(e.s=1),e},T.ceil=function(){return U(new a(this),this.e+1,2)},T.comparedTo=T.cmp=function(e,n){return M=1,i(this,new a(e,n))},T.decimalPlaces=T.dp=function(){var e,n,r=this.c;if(!r)return null;if(e=((n=r.length-1)-t(this.e/y))*y,n=r[n])for(;n%10==0;n/=10,e--);return 0>e&&(e=0),e},T.dividedBy=T.div=function(e,n){return M=3,C(this,new a(e,n),P,k)},T.dividedToIntegerBy=T.divToInt=function(e,n){return M=4,C(this,new a(e,n),0,1)},T.equals=T.eq=function(e,n){return M=5,0===i(this,new a(e,n))},T.floor=function(){return U(new a(this),this.e+1,3)},T.greaterThan=T.gt=function(e,n){return M=6,i(this,new a(e,n))>0},T.greaterThanOrEqualTo=T.gte=function(e,n){return M=7,1===(n=i(this,new a(e,n)))||0===n},T.isFinite=function(){return!!this.c},T.isInteger=T.isInt=function(){return!!this.c&&t(this.e/y)>this.c.length-2},T.isNaN=function(){return!this.s},T.isNegative=T.isNeg=function(){return this.s<0},T.isZero=function(){return!!this.c&&0==this.c[0]},T.lessThan=T.lt=function(e,n){return M=8,i(this,new a(e,n))<0},T.lessThanOrEqualTo=T.lte=function(e,n){return M=9,-1===(n=i(this,new a(e,n)))||0===n},T.minus=T.sub=function(e,n){var r,i,o,u,s=this,f=s.s;if(M=10,e=new a(e,n),n=e.s,!f||!n)return new a(0/0);if(f!=n)return e.s=-n,s.plus(e);var l=s.e/y,c=e.e/y,h=s.c,g=e.c;if(!l||!c){if(!h||!g)return h?(e.s=-n,e):new a(g?s:0/0);if(!h[0]||!g[0])return g[0]?(e.s=-n,e):new a(h[0]?s:3==k?-0:0)}if(l=t(l),c=t(c),h=h.slice(),f=l-c){for((u=0>f)?(f=-f,o=h):(c=l,o=g),o.reverse(),n=f;n--;o.push(0));o.reverse()}else for(i=(u=(f=h.length)<(n=g.length))?f:n,f=n=0;i>n;n++)if(h[n]!=g[n]){u=h[n]0)for(;n--;h[r++]=0);for(n=N-1;i>f;){if(h[--i]0?(s=u,r=l):(o=-o,r=f),r.reverse();o--;r.push(0));r.reverse()}for(o=f.length,n=l.length,0>o-n&&(r=l,l=f,f=r,n=o),o=0;n;)o=(f[--n]=f[n]+l[n]+o)/N|0,f[n]%=N;return o&&(f.unshift(o),++s),I(e,f,s)},T.precision=T.sd=function(e){var n,t,r=this,i=r.c;if(null!=e&&e!==!!e&&1!==e&&0!==e&&(j&&L(13,"argument"+w,e),e!=!!e&&(e=null)),!i)return null;if(t=i.length-1,n=t*y+1,t=i[t]){for(;t%10==0;t/=10,n--);for(t=i[0];t>=10;t/=10,n++);}return e&&r.e+1>n&&(n=r.e+1),n},T.round=function(e,n){var t=new a(this);return(null==e||H(e,0,E,15))&&U(t,~~e+this.e+1,null!=n&&H(n,0,8,15,v)?0|n:k),t},T.shift=function(e){var n=this;return H(e,-S,S,16,"argument")?n.times("1e"+c(e)):new a(n.c&&n.c[0]&&(-S>e||e>S)?n.s*(0>e?0:1/0):n)},T.squareRoot=T.sqrt=function(){var e,n,i,o,u,s=this,f=s.c,l=s.s,c=s.e,h=P+4,g=new a("0.5");if(1!==l||!f||!f[0])return new a(!l||0>l&&(!f||f[0])?0/0:f?s:1/0);if(l=Math.sqrt(+s),0==l||l==1/0?(n=r(f),(n.length+c)%2==0&&(n+="0"),l=Math.sqrt(n),c=t((c+1)/2)-(0>c||c%2),l==1/0?n="1e"+c:(n=l.toExponential(),n=n.slice(0,n.indexOf("e")+1)+c),i=new a(n)):i=new a(l+""),i.c[0])for(c=i.e,l=c+h,3>l&&(l=0);;)if(u=i,i=g.times(u.plus(C(s,u,h,1))),r(u.c).slice(0,l)===(n=r(i.c)).slice(0,l)){if(i.el&&(m=O,O=S,S=m,o=l,l=g,g=o),o=l+g,m=[];o--;m.push(0));for(w=N,v=A,o=g;--o>=0;){for(r=0,p=S[o]%v,d=S[o]/v|0,s=l,u=o+s;u>o;)c=O[--s]%v,h=O[s]/v|0,f=d*c+h*p,c=p*c+f%v*v+m[u]+r,r=(c/w|0)+(f/v|0)+d*h,m[u--]=c%w;m[u]=r}return r?++i:m.shift(),I(e,m,i)},T.toDigits=function(e,n){var t=new a(this);return e=null!=e&&H(e,1,E,18,"precision")?0|e:null,n=null!=n&&H(n,0,8,18,v)?0|n:k,e?U(t,e,n):t},T.toExponential=function(e,n){return _(this,null!=e&&H(e,0,E,19)?~~e+1:null,n,19)},T.toFixed=function(e,n){return _(this,null!=e&&H(e,0,E,20)?~~e+this.e+1:null,n,20)},T.toFormat=function(e,n){var t=_(this,null!=e&&H(e,0,E,21)?~~e+this.e+1:null,n,21);if(this.c){var r,i=t.split("."),o=+X.groupSize,u=+X.secondaryGroupSize,s=X.groupSeparator,f=i[0],l=i[1],c=this.s<0,a=c?f.slice(1):f,h=a.length;if(u&&(r=o,o=u,u=r,h-=r),o>0&&h>0){for(r=h%o||o,f=a.substr(0,r);h>r;r+=o)f+=s+a.substr(r,o);u>0&&(f+=s+a.slice(r)),c&&(f="-"+f)}t=l?f+X.decimalSeparator+((u=+X.fractionGroupSize)?l.replace(new RegExp("\\d{"+u+"}\\B","g"),"$&"+X.fractionGroupSeparator):l):f}return t},T.toFraction=function(e){var n,t,i,o,u,s,f,l,c,h=j,g=this,p=g.c,d=new a(q),m=t=new a(q),w=f=new a(q);if(null!=e&&(j=!1,s=new a(e),j=h,(!(h=s.isInt())||s.lt(q))&&(j&&L(22,"max denominator "+(h?"out of range":"not an integer"),e),e=!h&&s.c&&U(s,s.e+1,1).gte(q)?s:null)),!p)return g.toString();for(c=r(p),o=d.e=c.length-g.e-1,d.c[0]=R[(u=o%y)<0?y+u:u],e=!e||s.cmp(d)>0?o>0?d:m:s,u=z,z=1/0,s=new a(c),f.c[0]=0;l=C(s,d,0,1),i=t.plus(l.times(w)),1!=i.cmp(e);)t=w,w=i,m=f.plus(l.times(i=m)),f=i,d=s.minus(l.times(i=d)),s=i;return i=C(e.minus(t),w,0,1),f=f.plus(i.times(m)),t=t.plus(i.times(w)),f.s=m.s=g.s,o*=2,n=C(m,w,o,k).minus(g).abs().cmp(C(f,t,o,k).minus(g).abs())<1?[m.toString(),w.toString()]:[f.toString(),t.toString()],z=u,n},T.toNumber=function(){var e=this;return+e||(e.s?0*e.s:0/0)},T.toPower=T.pow=function(e){var n,t,r=m(0>e?-e:+e),i=this;if(!H(e,-S,S,23,"exponent")&&(!isFinite(e)||r>S&&(e/=0)||parseFloat(e)!=e&&!(e=0/0)))return new a(Math.pow(+i,e));for(n=J?d(J/y+2):0,t=new a(q);;){if(r%2){if(t=t.times(i),!t.c)break;n&&t.c.length>n&&(t.c.length=n)}if(r=m(r/2),!r)break;i=i.times(i),n&&i.c&&i.c.length>n&&(i.c.length=n)}return 0>e&&(t=q.div(t)),n?U(t,J,k):t},T.toPrecision=function(e,n){return _(this,null!=e&&H(e,1,E,24,"precision")?0|e:null,n,24)},T.toString=function(e){var n,t=this,i=t.s,o=t.e;return null===o?i?(n="Infinity",0>i&&(n="-"+n)):n="NaN":(n=r(t.c),n=null!=e&&H(e,2,64,25,"base")?D(l(n,o),0|e,10,i):B>=o||o>=$?f(n,o):l(n,o),0>i&&t.c[0]&&(n="-"+n)),n},T.truncated=T.trunc=function(){return U(new a(this),this.e+1,1)},T.valueOf=T.toJSON=function(){return this.toString()},null!=e&&a.config(e),a}function t(e){var n=0|e;return e>0||e===n?n:n-1}function r(e){for(var n,t,r=1,i=e.length,o=e[0]+"";i>r;){for(n=e[r++]+"",t=y-n.length;t--;n="0"+n);o+=n}for(i=o.length;48===o.charCodeAt(--i););return o.slice(0,i+1||1)}function i(e,n){var t,r,i=e.c,o=n.c,u=e.s,s=n.s,f=e.e,l=n.e;if(!u||!s)return null;if(t=i&&!i[0],r=o&&!o[0],t||r)return t?r?0:-s:u;if(u!=s)return u;if(t=0>u,r=f==l,!i||!o)return r?0:!i^t?1:-1;if(!r)return f>l^t?1:-1;for(s=(f=i.length)<(l=o.length)?f:l,u=0;s>u;u++)if(i[u]!=o[u])return i[u]>o[u]^t?1:-1;return f==l?0:f>l^t?1:-1}function o(e,n,t){return(e=c(e))>=n&&t>=e}function u(e){return"[object Array]"==Object.prototype.toString.call(e)}function s(e,n,t){for(var r,i,o=[0],u=0,s=e.length;s>u;){for(i=o.length;i--;o[i]*=n);for(o[r=0]+=O.indexOf(e.charAt(u++));rt-1&&(null==o[r+1]&&(o[r+1]=0),o[r+1]+=o[r]/t|0,o[r]%=t)}return o.reverse()}function f(e,n){return(e.length>1?e.charAt(0)+"."+e.slice(1):e)+(0>n?"e":"e+")+n}function l(e,n){var t,r;if(0>n){for(r="0.";++n;r+="0");e=r+e}else if(t=e.length,++n>t){for(r="0",n-=t;--n;r+="0");e+=r}else t>n&&(e=e.slice(0,n)+"."+e.slice(n));return e}function c(e){return e=parseFloat(e),0>e?d(e):m(e)}var a,h,g,p=/^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,d=Math.ceil,m=Math.floor,w=" not a boolean or binary digit",v="rounding mode",b="number type has more than 15 significant digits",O="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_",N=1e14,y=14,S=9007199254740991,R=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],A=1e7,E=1e9;if(a=n(),"function"==typeof define&&define.amd)define(function(){return a});else if("undefined"!=typeof module&&module.exports){if(module.exports=a,!h)try{h=require("crypto")}catch(D){}}else e.BigNumber=a}(this); +//# sourceMappingURL=doc/bignumber.js.map diff --git a/signer/rules/deps/bindata.go b/signer/rules/deps/bindata.go new file mode 100644 index 0000000000..0b27f45172 --- /dev/null +++ b/signer/rules/deps/bindata.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. +// sources: +// bignumber.js +// DO NOT EDIT! + +package deps + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _bignumberJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\xbc\x6b\x77\x9b\xc8\x93\x38\xfc\x7e\x3f\x85\xc4\xc6\x9c\x6e\x53\x20\x90\x9d\x38\x86\x14\x9c\x4c\x62\xe7\xe7\x79\x1c\x3b\x4f\x9c\xcc\xcc\xae\xa2\xc9\x91\x51\x23\x75\x82\x40\xe1\x62\xc7\x09\xfe\x7d\xf6\xff\xa9\x6e\x40\xf2\x25\xbb\xb3\x6f\x2c\xe8\x4b\x75\x75\x75\xdd\xbb\xf0\x68\x77\x70\x29\x17\x59\xbd\xba\x14\x85\xf3\xa5\x1c\x5c\x8d\x1d\xd7\xd9\x1b\x2c\xab\x6a\x5d\xfa\xa3\xd1\x42\x56\xcb\xfa\xd2\x89\xf3\xd5\xe8\xad\xfc\x2a\xde\xc6\xe9\x68\x7b\xf8\xe8\xf4\xe4\xd5\xd1\xd9\xab\xa3\xc1\xee\xe8\x3f\x46\xbb\x83\x55\x3e\x97\x89\x14\xf3\xc1\xe5\xcd\xe0\x87\x48\xe5\x62\x50\xe5\x83\x44\x7e\x7f\x0c\x5c\x91\x5f\x8a\xa2\xfa\x5a\xc8\x95\xc8\x46\x79\x55\xe5\xff\x59\x88\x45\x9d\xce\x0a\x5b\x7c\x5f\x17\xa2\x2c\x65\x9e\xd9\x32\x8b\xf3\xd5\x7a\x56\xc9\x4b\x99\xca\xea\x86\x96\x19\x26\x75\x16\x57\x32\xcf\x98\xe0\x3f\x8d\xba\x14\x83\xb2\x2a\x64\x5c\x19\x41\xd7\x31\x50\x5d\xfd\xdb\x8c\x09\xc8\xf8\xcf\xab\x59\x31\xa8\xa0\x00\x09\x39\xd4\x50\x42\x82\xd5\x52\x96\x81\x4c\xd8\x90\x25\x03\x99\x95\xd5\x2c\x8b\x45\x9e\x0c\x66\x9c\x17\xa2\xaa\x8b\x6c\xf0\xc5\x34\x4f\xd9\xf8\x19\x18\x71\x9e\x95\x55\x51\xc7\x55\x5e\x0c\xe2\x59\x9a\x0e\xae\x65\xb5\xcc\xeb\x6a\x90\x89\x6b\x03\x04\x87\x4c\x5c\xb7\xeb\x10\xc0\xac\x4e\xd3\x21\x66\xa6\xf9\x2f\x96\xc1\x18\x9e\xed\xc3\x5b\x30\x2e\x67\xa5\x30\x38\xff\x49\xfd\xe8\x36\x19\x94\x28\x2c\xc3\x00\xcf\x45\xcc\xba\x15\x13\x6c\x21\xdd\x41\x28\x12\x7e\xc9\xe1\x23\x4b\xe0\x9d\x95\x38\xc2\xf2\xe0\xab\x5a\x87\xe5\x68\xe8\xa3\x30\x10\xab\x9b\x35\x0d\x16\xdc\x34\xdd\x5d\x31\x44\xb7\x69\x86\x04\xec\xbd\x58\x1c\x7d\x5f\x33\xe3\x6f\x3b\x32\x2c\x56\xa1\x31\x31\xac\x73\xa7\x4c\x65\x2c\x98\x0b\x19\xb7\x8c\xa9\x65\x70\xcb\x60\x91\xff\xe9\x93\x63\x58\x95\x65\xf0\xe8\x89\x01\x7b\x07\x61\x16\x19\xd2\xf0\x0d\x83\x3b\x95\x28\x2b\x56\xf6\x84\x59\xb0\x04\x4a\xc8\x69\xbb\x79\xc4\x12\xa7\x44\x37\xf4\x46\x22\x62\x25\x96\x2d\x68\x8f\x83\xed\x71\xdf\x83\x2f\xa6\x59\x3a\x85\x58\xa7\xb3\x58\xb0\xd1\xdf\xee\x27\xc7\xdd\x6d\x3e\x39\x23\x20\xb8\xa9\xc8\x16\xd5\x32\xf4\x9e\x12\xa5\xdf\xc2\x25\xd1\x32\xc7\xa1\xc7\x7d\x02\xba\xff\x14\x11\x4b\x27\x5e\xce\x8a\x57\xf9\x5c\xbc\xac\x98\xcb\x1f\x5d\xa3\xc4\xd7\xac\x04\xcf\x85\x0c\x12\xa7\xe4\xb7\x22\x2d\x05\x11\xfa\x2e\x19\x7b\x22\x3b\x25\x0a\xa7\x84\xc4\x11\x28\x1c\x01\x89\x13\x23\xa3\xc7\x98\x47\xa2\x05\xcd\x7d\x01\x57\xb9\x9c\xb3\xb7\xe8\xfe\x6f\xb4\x46\x74\xd5\xb1\x6e\xd1\x41\xa0\x2d\x5a\xdc\x04\x22\xfe\xfb\xdf\xc4\x90\x79\xc1\x0a\x74\x41\xa2\x08\x64\x88\x9e\x1b\xc8\x11\x7a\x2e\x14\x96\xc5\x83\x1e\x35\x81\x85\x42\x68\x22\xa6\x1b\x04\x6e\x35\xaf\xf4\xfb\x1a\xae\xdb\x13\x51\xcd\xf7\x8f\x85\x07\xff\x17\xe2\xdd\xde\x12\x62\xac\xc0\xd2\x91\xd9\x5c\x7c\x3f\x4f\x98\xe1\x18\x9c\x87\xb6\x67\x9a\x6a\x7c\x77\x78\x86\x63\xd0\xa1\x71\x60\x92\xa0\x88\x59\x11\x2f\xd9\x48\x8c\x24\xe7\xa1\x1b\x31\x37\x2c\x4c\x93\x15\x28\x39\x14\x16\x5a\xdd\x3a\xd2\xf2\x38\xa8\x65\xeb\x4b\x92\xd4\x6c\xc1\x5c\x90\x9c\xfb\xdd\xf8\xb2\xe5\x02\x0e\x12\xdd\x60\xff\xf9\x7d\xb4\x25\x0f\x24\x91\x88\xd0\xac\xfb\xd1\x8f\x0c\xb4\xed\x9a\x07\xea\xb0\x36\xbb\x94\x50\x5b\x1e\xe7\x32\xd9\x9a\x0a\xb9\x69\x7e\x31\xcd\x7a\x8b\xed\x12\xa7\xdc\x15\x1c\x0a\x2c\x6c\x69\x7b\x50\x84\x3f\x38\x1d\x02\x1d\x07\x09\x73\x40\x84\x1f\xc8\x84\xbd\x09\x0b\xd5\x31\xa1\x1e\x77\x1a\x74\x07\xb2\x75\x6e\x53\x90\xc8\x0a\xcb\xe3\x3b\x37\xa0\xb7\x28\x2d\xbc\xe1\x50\x87\x52\xf3\x80\x34\xcd\xc4\x89\x9d\x75\x5d\x2e\x59\x4f\x25\x45\x12\xa8\x6d\xbc\x09\xea\x50\x06\xfc\xe1\x08\x09\x0a\x0e\x0f\xb6\x36\x47\x24\xbb\xb1\xbb\x7d\xdd\x6a\x2c\x6d\xac\x15\xad\x02\x69\xdb\x41\x69\xa1\xe1\x1a\xc4\x11\x3d\x3c\x2d\x1e\x83\xed\x6d\xbc\x45\xf7\xb6\xd7\x97\xaf\x49\x8f\x41\x05\x52\xeb\x4c\xd2\x96\x09\xc4\xb0\x84\x05\xac\x61\x8e\xe2\x0e\x9b\xc0\x0a\xdf\xc1\x35\x7e\x55\x2b\xee\x1d\x84\x95\x69\x2a\x51\xaa\xf2\xd3\xfc\x5a\x14\xaf\x66\xa5\x60\x9c\xc3\x3c\x44\xd7\x34\x59\x82\xbf\xc3\xef\xe8\x02\x8d\xb8\xc7\x55\xb0\x6e\x55\x5f\xc5\x61\x89\x6b\x67\x9d\x5f\x33\xd1\x6e\xcc\x9e\x73\xf8\x1d\x13\x58\x3b\x31\x96\x2c\x65\x05\x5b\x3a\x31\x87\xa5\x23\xb8\x12\x7a\x0e\x6b\x47\xe0\xda\x89\x7b\x4e\x5a\x60\xc9\x04\x54\xd4\x55\x63\x82\x8b\x8e\x69\x5c\xc4\xc5\xc4\xb6\x93\x69\xb0\x70\xd6\xf9\x9a\x71\xc5\x2e\xc3\xc5\xc4\x9d\xb6\x42\x64\xb8\x06\x35\xb9\xe1\x3c\xb2\xed\xda\xa7\x95\x70\x41\x4b\x61\x0d\x4b\xa7\x44\x09\x4b\x7c\xc5\x96\xb0\x86\x15\x5c\x13\xfc\x05\x2e\x9d\x18\x62\x5c\x3a\x05\xd4\xa8\x70\xca\xb1\xb6\x56\x96\x07\x73\x5c\x4c\xf2\x29\x24\x98\x8d\xc6\x10\x63\xdc\x34\x6e\x98\x37\x8d\x36\x0f\x8b\x49\x6e\x79\x53\x88\x71\x3f\xbc\x8e\x5a\x93\x31\x6f\x9a\x98\x9b\x26\x73\x11\xaf\x9b\xe6\x1a\x91\x2d\x9d\xf2\x85\x1b\xed\xf9\x63\xce\xfd\x79\x98\x34\xcd\x1c\x31\x31\x4d\xb6\xaf\x46\xc4\x4d\xf3\x0c\xf1\xda\x34\x3d\x73\x31\xc9\x6d\x6f\xba\x3d\xe9\xb9\x7f\xc0\x39\x78\xb4\xa2\xde\xa0\xc0\x38\x4a\x99\xe1\x19\x60\xaf\xb8\x4f\x1b\xed\xd8\xb7\xa3\x0f\xe6\x10\x73\x3a\x49\xdb\xce\x02\xcb\x22\x52\xe5\xd3\x30\x0b\x38\xed\x03\x5d\xc8\x9b\x86\x59\x56\x0d\x0b\xa7\xce\xca\xa5\x4c\x2a\xe6\x71\x2d\x98\x5b\x34\x1e\xb6\x14\xd6\x1d\x73\x75\xdc\x86\x11\x24\x21\xce\x03\x61\xe1\xb9\x12\xd9\x97\x15\x5b\x4c\xe6\x96\x35\xe5\x3c\x10\x98\x32\x01\x35\xbf\x6d\xd5\x98\xd8\xf0\xe2\xe7\x87\xbc\x58\x12\x2f\xd2\x11\x55\xa8\x89\x56\x91\x9d\xad\xc0\x85\xe7\x20\xe1\x8a\x47\x6e\x53\xf9\x5f\x61\x48\xea\xbc\x03\xe8\x54\xf9\x85\x56\x3d\xea\xbc\x73\xd2\xf5\x13\x77\x4a\x26\xd8\x11\x40\x60\xc8\x06\x2f\xb1\x60\x42\x31\x16\x7a\x87\x88\xb2\x69\xc6\xfb\x88\xd2\x34\x7f\x0b\xb1\x8c\x12\xb6\x84\x92\xfb\xa9\xfa\xe9\x15\x82\xc0\x8f\xac\x35\xd9\x9c\x30\x25\x7e\x23\x98\x3d\x2c\x62\x8c\x56\xed\xdc\x05\xca\xea\x10\xb3\xa6\xf9\x2d\xc4\x9a\x6b\xc5\x10\x64\x61\x1c\x2c\x95\xc0\x42\x4c\x1a\x6f\x89\xb4\x68\xdd\x0a\x2c\x39\x0e\x36\x96\xb0\xc4\x54\xb5\x92\x66\x0b\x63\x65\x79\x6c\x3b\x0b\x5d\x75\x70\x34\xdd\x31\x82\xcc\xb6\x5b\x48\x3c\xd8\xcc\xb6\xb0\xb6\x63\xe8\x86\xd6\x96\x87\x18\x9b\x66\x3b\x87\xdf\x99\xd4\x53\xae\x7c\xe1\x9a\x66\x1e\x19\xb6\x61\x2d\xfd\xe5\xe6\x64\xbe\xdf\xf3\xaa\xd0\xd5\x0a\x9a\x09\x62\x35\xad\x05\xe8\x09\xaa\xce\xa5\xa1\xb7\xc0\xb2\xe4\x8b\x4e\xac\x03\x85\x7b\xd1\xf7\xcb\x29\x87\x61\xe1\x94\xfc\x67\x85\x45\x70\x59\x88\xd9\xd7\xdb\xcc\x21\x7f\x8b\x55\x50\x10\xcc\x0a\x8b\x9e\x4b\xaa\x0d\x2e\xc7\x2d\x97\x14\xc4\x27\xba\x9b\x65\xa1\x68\x1a\x11\x56\x4d\x23\x86\x18\x33\xc1\x39\xe9\xfa\x02\x98\x6c\x1a\x63\x2e\x62\xb9\x9a\xa5\x03\xa5\x81\x4a\x83\x5b\xfd\xf0\xc8\x18\x90\x5f\x97\x27\x83\x62\x96\x2d\x84\xe1\x1b\x83\x2c\xaf\x06\xb3\x6c\x20\xb3\x4a\x2c\x44\x61\x70\xf2\x51\x86\x5b\xfa\xf2\x44\xaf\xae\xcf\x90\xe8\x51\xa0\x07\x12\xb3\x5e\x1e\xb2\x89\x6d\xcb\x69\x90\x75\x1a\x47\x19\x01\xcc\x26\xee\xf4\x57\x7e\x00\x6d\xd4\xaa\x76\x6f\x6c\x8f\x87\x3f\x22\xe1\xc4\xc4\x53\x8a\xdd\xfd\x37\x61\xa5\x1a\x26\x42\xa9\x6e\x9f\xd1\x6f\x05\xd4\x94\x71\xd8\x12\x9d\xd3\x0e\x2d\x8d\x12\x11\xf9\xa8\x28\xf2\x82\x4d\x0c\x7a\xfe\x4d\x2e\xce\xb4\x3b\x03\x46\xbc\x5a\x1b\xca\xc9\x4d\xe4\xc2\x00\x63\x2e\xaf\xf4\xdf\x0f\xf9\x49\x56\x19\x60\x88\x6f\x06\x18\x8b\x4a\xfd\x11\x06\x18\x69\xa5\xfe\xd0\xe3\x4a\x66\x75\x49\xbf\xf9\xdc\x00\x63\x9d\xaa\x97\x75\x21\x62\x49\xfe\xbb\x01\x46\x31\xcb\xe6\xf9\x8a\x1e\xf2\x3a\xa3\x31\x4a\x6f\x18\x60\x54\x72\x25\x68\x70\x95\xbf\x96\x0b\x59\xe9\xc7\xa3\xef\xeb\x3c\x13\x59\x25\x67\xa9\x7a\x3f\x96\xdf\xc5\x5c\x3f\xe5\xc5\x6a\x56\xe9\xc7\x62\xa6\xb6\x48\x2b\xe5\xd7\xaa\xe9\xdd\xd6\x8a\x9d\xac\x1b\x60\x6c\x36\x39\x9d\x88\xa9\x65\x30\x3e\x30\xac\xcc\x32\xfc\x81\x61\x55\x3c\xa8\x96\x45\x7e\x3d\x28\x9c\x6c\xb6\x12\xb8\x19\xac\xe9\x64\xc0\x5b\x74\xa1\xd8\x10\xf4\x63\xc7\x65\x9a\xa4\x7d\x1c\x01\x29\xc4\x30\x23\x95\x02\x4b\x7c\x4f\xfa\x65\xc6\x7f\x0a\x5f\xdb\x7a\x24\xe7\x74\x46\x47\x5d\xaa\xa3\x2e\xd5\x51\x2b\x7f\x46\x29\xa2\xcc\x96\xe0\x86\x39\xcf\x2d\xbc\x81\x1a\x33\x48\x70\x36\x49\xd1\x25\xc3\x90\x8c\x96\x13\x69\xd7\xb6\x37\xdd\xf1\xdc\xc6\xed\x75\x4e\x8a\x73\xc6\x72\xcb\xe3\xa3\x1b\x0e\x69\x88\xb3\xce\xec\x29\xd7\xb0\xe0\x4a\x72\x06\x42\x3b\x01\x5d\xe7\x0b\x4c\x83\x99\x76\x01\x5c\xe2\x41\x8c\x95\x2b\xea\x41\xbe\xa3\x56\xce\xed\x1b\xcb\xd3\x0e\xa6\xd6\xe7\x84\x76\x4a\xce\x8c\xf7\x10\xf5\xad\x39\x12\x62\x74\xc3\x3a\x72\xfd\x7b\xe8\xde\x2a\xd9\x2e\xc8\xe6\x65\x9d\xcd\x9b\x4d\x52\x8b\x8c\x14\xa3\x19\x89\x9f\xec\x74\x33\xc8\xf5\xda\x0f\xab\x88\xc5\x4d\x53\xb4\x16\xb0\x6a\x9a\x0a\x91\x89\x2d\x0b\x18\x87\x4f\x9b\xe6\xa9\xd6\x5a\xfb\x6a\x44\xa1\x2c\x20\x79\x1d\x79\xe8\x46\x75\xe8\x46\x2d\x1a\x53\xdf\xf5\x67\x93\x94\x60\xef\x78\xae\xe9\x6d\x03\xeb\x2c\x63\xd6\x34\xc3\xd9\xc6\xf4\x0f\x3a\x5a\xd1\xb9\x47\xa4\x6c\x85\x0a\xb6\x68\x08\x2e\x27\xd9\xce\xcd\x14\x48\xda\xec\xac\x69\x5c\xee\xab\x66\x25\x85\x20\x94\xcb\x80\x98\x47\xac\x87\x91\x42\x89\x1e\xa4\xb6\xcd\xfd\xad\x46\x8b\xf8\x61\x39\xb9\xb1\xf3\x29\x10\x7d\x91\x50\x5e\xb1\x0e\xe9\x9d\xe5\xa4\x9e\xf2\xdd\xd2\x77\x39\x14\x4a\x4b\x07\x5a\x4b\xba\x88\xa9\xd6\x30\x39\x7a\x50\x6b\x96\xaa\xd5\xb9\xd4\xea\x5c\xf2\x8d\x8b\x4c\x7d\x16\x96\xb4\xfe\x9d\x21\xa5\x3a\xba\x21\x96\xa4\x9d\x1d\x61\x59\x7a\x67\x78\x66\x9a\x4c\x3d\x91\x31\xd7\x6a\x97\x98\x78\x92\x2a\x28\xf4\x3b\xc4\x33\xcd\x55\x01\x91\xd4\x26\x57\xa0\x44\xef\x56\xa3\x33\xdb\x72\xae\x70\xa6\x5c\x06\xe2\x34\xad\xeb\x6e\x85\x23\xee\xab\x30\xe1\x88\x17\x6f\x14\x0e\xbd\x1a\xdb\xb2\xfd\x24\x5b\xaf\x94\xec\x7d\xc0\x99\xb3\x2e\xf2\x2a\xa7\x70\x0b\xbe\xb5\x76\xc2\xe3\xf0\x0e\xc7\x2e\x7c\xc5\x7d\xf8\x0d\xed\x03\x78\x82\x63\x0f\xde\xa0\xed\x89\x03\xf8\x81\xf4\xf7\x0b\x0e\x5d\xf8\x17\x1e\xc3\x1f\x38\xf4\xe0\x4f\xf4\xe0\x77\xf4\x5c\x17\xfe\xc2\x9f\xad\xe6\xbf\x10\xeb\x59\x31\xab\xf2\xc2\x27\xf7\x73\x51\xe4\xf5\x7a\xab\x09\xba\x26\xf9\x43\xf8\x7b\x50\x8a\x38\xcf\xe6\xb3\xe2\xe6\x4d\xdf\xe8\x42\xd2\x2a\xa1\x37\xf7\xe6\x0e\x8c\x7b\x5d\x6a\xf8\x6d\xd0\xb3\xd8\x2c\xcb\xab\xa5\x28\x30\x83\x99\xf3\xfe\xfc\xe3\xd9\xeb\xcf\x1f\xdf\xa1\xdb\xbf\xbc\x3e\xff\xf3\x0c\xbd\xfe\xf5\xd5\xd1\xc9\x29\x8e\xfb\xd7\xe3\xd3\xf3\xf3\xf7\xb8\xd7\xbf\xff\xeb\xe5\xe9\x31\xcd\xdf\xbf\xdb\xa2\x80\x3c\xbd\xdb\x76\xf4\xc7\xd1\x19\x3e\xbb\xdb\xa6\xa0\x1f\xdc\x6d\xd3\x4b\x3c\x87\x99\x73\xf4\xf1\xd5\xe9\xc9\x6b\x3c\x84\x99\xa3\x6d\x03\xf6\xa9\x17\xad\x02\x95\x3e\x24\x61\xc1\x9f\xb7\x20\x71\x56\x2c\xea\x95\xc8\x2a\xe2\x3c\x49\xee\x55\x42\xac\x66\xe4\x97\x5f\x44\x5c\x6d\xa2\xe6\x32\xda\x02\xd3\x92\xa5\x74\x96\xb3\xf2\xfc\x3a\x7b\x57\xe4\x6b\x51\x54\x37\x2c\xe3\x91\x56\x19\x4c\x60\x39\xc9\xa6\xdc\xa7\x60\x78\xe0\xde\xfa\x0f\x27\xcb\x2e\x8d\x50\x6d\xe6\xc8\x49\x45\xce\x65\x37\xab\x8f\xaf\x59\x86\xc6\xeb\xa3\x57\x27\x6f\x5f\x9e\x7e\x7e\x77\xfa\xf2\xd5\xd1\x85\xc1\xc9\x7f\x14\xe0\xc2\x11\x8c\x21\x23\xe5\xf3\x0e\xdd\x86\xa2\xc1\x49\x36\xc5\x77\xa0\xe6\x28\x02\x9d\x9c\xbd\xf9\xfc\xf6\xfc\xf5\xd1\x66\xca\xf3\x6e\xca\xd7\xad\x29\x5f\xf5\x94\xa3\xbf\xde\x9d\x9f\x1d\x9d\x7d\x38\x79\x79\xfa\xf9\xe5\x07\x9a\x43\xde\x11\x8f\xfe\xa5\x5c\x21\xb0\x8f\xc0\x6d\x67\x53\x8b\x37\xdd\xc6\xe0\x37\x02\x47\xa3\x9e\xa8\x07\x6f\xca\x7d\x5a\xd0\x3e\xda\x1e\x62\x33\xea\x65\x6e\x28\x22\x5b\xf8\x82\x73\xde\x22\x30\xf9\x0d\x9e\x4c\x5b\xbc\x5f\x9e\xbd\x39\x7a\x6c\x6d\xdb\xbb\xbb\xb8\xb7\x81\xfc\xa6\x5b\xfc\xc7\x2f\x17\x77\x1b\x11\xbd\x41\x9b\xfd\xb8\x8b\x80\xaf\x33\x66\x90\x59\xc6\x20\x9e\x65\xe4\x39\x5d\x8a\xc1\x0f\x51\xe4\x06\x88\x0d\x7a\x6f\xe0\x47\x8b\xde\xd1\xfb\xf7\xe7\xef\xd5\x11\x30\x81\x88\xc3\xa1\x68\x1a\x0f\x11\x45\xd3\x90\x36\x11\x11\x23\x45\xf0\x2f\x64\x5f\xa8\x8f\x47\xc7\x7e\xbe\xb5\xc8\x35\x01\xd5\x30\xbf\x68\x78\xaf\xde\xff\xd7\xbb\x0f\xe7\xff\x13\xbc\x3f\x70\xc8\xa8\x75\xb8\x6c\x9a\x8e\x35\x87\x1d\x6b\x2e\x39\x08\xd3\x1c\xfe\xa1\xf2\x03\xb4\x86\x11\x17\x37\xeb\x2a\x1f\xd4\xd9\xec\x6a\x26\xd3\xd9\x65\x2a\x0c\x58\xf2\xc7\x71\xf8\x43\xe3\xf0\xf6\xfc\xf5\xc7\xd3\xf3\x7b\x8c\x72\xd8\x51\xee\xcf\x2d\x46\xf9\x53\x4f\x78\x77\xfe\xe7\xe7\x77\xef\x8f\x5e\x9d\x5c\x9c\x9c\x9f\x3d\xc2\x8e\xbf\x6f\x4d\xf9\x5d\x4f\x39\x3e\x7f\xff\xb6\xe5\xa9\x07\xf2\x25\xa2\xbf\x50\x6c\x9f\x44\xeb\xc0\xb6\xe3\x36\xf8\xfe\x05\xc5\x2d\xcc\x9c\xd5\xec\x3b\x3e\x14\xaa\xef\x6c\x23\xce\x1f\x9c\xb4\xe2\x6a\xa8\xcc\xfe\xd7\xa1\x0b\x3d\x54\xfb\x7d\x0f\x34\x06\x1e\xba\xee\x81\x77\x78\x38\x7e\xba\x7f\xb0\xef\x1e\x1e\x8e\x21\xc3\xb7\xb3\x6a\xd9\x8e\x67\x7c\x57\x98\x63\xf7\xf0\xc0\x7b\xea\x3d\xa2\x26\x56\xec\xde\x58\xfe\x98\x3e\x78\xbe\xf7\xfc\xf9\x33\xf7\xf9\x2e\xf3\xdc\x83\xbd\x83\x7d\xef\xf9\x78\x7f\xf7\xce\xbc\xc6\xe5\x16\xeb\x46\xdd\xef\xd9\xe8\x8a\xad\x3c\xf3\xbd\xe4\x31\xba\x90\xe0\x64\x0a\x69\x6b\x93\xbe\x29\x6f\x4e\xb4\x01\xa9\xd8\x9c\xa0\xb7\x4f\xf1\xa8\xf0\xdf\x41\x8e\x73\x26\xc8\x61\xfb\x83\xcb\x84\x2d\x4d\x73\xe9\x2c\x44\xf5\x5e\xad\xfb\xc7\x2c\xad\x45\xa9\xcd\x7b\x85\x0f\x3a\x54\x80\xf9\x51\x66\xd5\xde\xf8\x65\x51\xcc\x6e\x58\xbe\x8b\x63\xce\x83\x3c\x2c\x03\x5e\xa3\xb7\xe7\xb9\x07\xe3\xdd\x6a\x52\x4e\x2d\x56\x4d\x4a\xcb\x9b\x86\x61\xe8\x79\x1c\xea\x10\x0f\x85\xf7\x34\x62\xc5\x3f\x00\x3a\xe6\x1c\x08\x06\x16\x24\xfa\x1a\x0e\x16\x4a\xfa\x59\xa2\x1d\xc7\x7a\xc7\x13\xde\x3e\x87\xd2\xc2\x31\x0f\x4a\xcc\x47\xe3\x3e\xb8\x54\x3b\xd2\x64\xfc\xed\xa6\xda\xde\xcd\x56\x23\x61\x7e\xd0\x23\x3e\x7e\xee\xed\x1f\xec\x1f\x1e\x3c\x3b\xf0\xdc\x67\x4f\x9f\xed\xb2\x3d\xcf\x24\x0c\xb8\xe5\xb9\x87\x87\x4f\x3d\xef\xd9\xf8\xe0\xe0\xe0\xd9\xae\xc6\xc5\xda\x1f\x1f\xee\x1f\x3e\x3b\x18\x1f\xea\x96\xf1\xd4\xf2\x9e\x1d\x1c\x1c\x8c\x3d\xfd\xbe\xd7\xee\x7e\x7f\xfa\xe2\x85\xf7\x8c\xeb\x97\xa7\xd3\x17\x2f\x9e\x73\x8b\x1e\x9f\x4d\x7b\x7a\xdc\xc5\xe9\x80\x3b\x71\xbe\xbe\x61\x15\x85\xf7\x8f\x6c\xf5\x40\x6f\xf5\x40\x6f\x55\xc9\x95\xb7\xff\x2b\xcd\xa0\xd2\x49\xa5\xf6\xdc\xda\x6d\x66\x8c\x03\x2d\x1b\xd6\xa6\xc9\x92\x49\x69\x59\x53\x6c\xc1\x07\xda\x83\x4a\x26\xb6\x5d\x4e\x41\x90\x57\x9d\x9b\xa6\x20\x6d\x8d\xef\x27\x37\xb6\x98\x42\x42\x47\xb2\x62\xf9\xa8\xe6\xbb\x35\x57\x3e\x16\x35\x05\x89\xf6\xb0\xa0\xb4\x6d\xae\x13\x56\x25\x4f\x70\x22\xfb\xac\xa4\x0e\x3f\x6c\xaf\x9d\xe2\xd2\x14\x9d\xb3\xe1\x20\x6d\xbc\xd1\x8b\x97\xca\x9b\x4c\xee\x7b\x93\xca\x55\xbc\x09\xc9\x53\xa4\xb1\x76\xd9\x3b\x68\xa9\x23\x50\x42\xea\xc4\x98\x40\x7a\x7b\xcb\x38\xbc\xda\x16\xf2\x3e\x5a\x12\x77\xc2\xcf\x3b\x82\xd3\xc5\xff\x24\x3e\x3b\x2f\x21\xc6\x6c\xf4\xb2\xd1\xe9\x03\x81\x7d\x02\x3e\x48\x6c\x3b\xe0\x39\x8a\x49\x32\xdd\x79\x09\xb5\x7a\xa0\x81\x50\x60\xbc\x9b\x5b\xf5\x6e\x0a\x12\xd3\xdd\xdc\x2a\x76\x5e\xee\xbe\xb4\xc8\xeb\x60\x72\x54\x29\xe1\x2e\x68\x20\xb7\xe2\xdd\x1a\x68\x1a\xca\x9d\xaa\x13\xeb\xd2\x34\x45\x9f\xbe\x2a\xef\x84\xcc\xd9\x83\x08\x4f\xe5\x99\x86\x58\xf0\x1c\xab\xb0\x88\x3c\xdf\xf6\x74\x18\xa6\xa9\x9b\xa3\x1b\x54\xa1\x54\xf9\x69\x52\x00\x13\x39\x1d\x62\x36\x91\x53\xfe\x93\x10\x97\xd3\x90\x5e\xf4\x34\xed\x58\xb7\x48\xe4\x9b\x45\x8b\xcd\xa2\x5d\x02\x41\x12\x58\xda\xbd\x98\x54\x53\x1b\x25\x48\xa4\xa7\x17\xd9\xa4\x22\x60\x2e\xd0\x1b\xca\xdd\xc2\x52\x03\xa8\x59\x07\x7b\x43\x32\xdb\xb4\xbf\xee\x5e\x25\x10\xdd\x99\xf3\xe0\xf6\xbe\x5e\xeb\x23\x58\xbd\xdd\x74\x93\xe4\x85\x6b\xb8\x82\x4b\x38\x87\x0b\x78\x0f\x2f\xe1\x08\x5e\xc3\x67\xf8\x0e\xc7\x28\x9d\x12\x31\x77\x4a\xb5\x25\x38\x41\xe9\xc4\x70\x8a\xb9\x13\xeb\x7b\xb4\x13\xd3\x3c\x51\x18\x9c\x9a\xe6\x29\x05\x56\x5d\x64\xa5\xd5\xa4\x74\x4a\xd3\xcc\xe9\x0f\x3b\x89\x86\xa7\x4d\x43\x83\x87\x48\x23\xfd\x53\x1e\x9d\x98\xa6\x8b\x48\x6d\x4d\x33\x3c\x8d\xdc\xdd\x63\xff\x78\xe4\xfa\xee\xc8\xd5\xbc\x7a\xd5\x6a\xdb\x63\x0e\x97\x78\xa5\x73\xed\x31\x4a\x47\xd8\xb9\x23\xe0\x18\x6b\x2b\xb6\x3c\x48\x9a\x86\x25\x78\x06\x31\x56\x4c\x3a\xa4\x72\xed\x8a\xe5\xea\x01\x8e\xf1\x78\x74\xd3\xb8\x1c\x96\xe8\x06\xa7\x93\xe5\x14\x91\x9d\x4c\x96\x53\x8a\xe7\x82\x65\x1b\x94\x53\x7b\xd8\x37\x9b\x66\x6c\xdb\xe0\x86\xc7\xfc\x52\x6b\x06\x8f\xc3\x02\x87\xee\x46\xc8\x8e\xf0\xa4\x63\xe8\xcf\x78\xda\x3d\x52\x10\x79\x6c\xe1\x18\xd6\x48\xe1\x1d\xa3\x4d\x5a\x1e\xe7\xb0\x0e\x3d\xd3\x64\xa7\x28\xd8\x29\xac\x21\xe1\x70\x82\x82\x9d\xe8\xc7\xad\xf9\x1b\xa8\x1c\x5e\xe2\x67\x38\xc7\x93\xfe\xaa\xe0\x33\x87\x0b\x3c\xef\xc2\xae\xcf\xe1\x45\x70\x3e\xb9\x20\xb5\xe2\xf2\xe0\x3b\x9e\x76\x12\x04\xdf\x7b\x3e\x77\x39\xbc\x56\x74\x86\xd3\x89\x37\x0d\x31\x19\x8d\x4d\xf3\xb5\x65\x05\xf3\x7c\xb0\x46\x97\x24\x91\x9d\xc2\x39\x7c\x86\x0b\x0e\x6e\x98\x46\xec\x3d\x9e\xd3\xf0\xcf\x43\xbc\x30\x4d\xf6\x1e\xdf\xef\x26\x16\x3b\x9f\x78\x8a\x28\x5c\xed\xea\xfd\xe8\xb5\xda\x4e\xc4\xd6\xa1\x4a\x4a\xaf\x31\xb1\x3d\x0e\xf3\xcd\xde\xae\x71\xde\x6d\x68\x83\xb1\x5a\x6d\x0e\xe7\x70\x4d\xab\x79\x88\x29\xcd\xb5\x6d\x28\xd8\x1c\xae\xc3\xcf\xd1\x77\xff\x14\xae\x21\xe1\x9c\xfb\x14\xf8\xae\x4d\x93\xa5\xb8\x46\x05\xba\xdf\xdd\x5d\xe0\xe1\xb5\x69\xce\xb7\xb7\x5b\xb0\x73\x98\xc3\x05\x21\x61\xb7\x4b\xdc\xc3\xa0\xdf\xaf\x17\x2a\x04\x2c\x4b\x4d\xba\x68\x11\xb8\x50\x08\x6c\xa1\xcd\x7d\xd2\xa4\xdd\xd0\x73\x54\xd9\xcd\xcb\xc9\x92\x08\xbf\x86\xd4\x34\x89\x60\x51\x7b\x12\x27\x93\x97\x44\x29\x9f\x9d\xe3\x84\x9e\xa7\x70\x81\x1e\x0f\xae\x97\x32\x15\x8c\xbd\xb4\xac\x17\x47\x5d\x52\xe4\x5c\x27\x4c\x8f\x49\x91\x2f\x70\xd3\x06\x97\x4a\x12\x2e\x3b\x09\xa6\xa0\x3c\x41\x3c\xd3\x7a\x62\x89\x1e\x1c\x23\x0d\x09\x8e\x95\xe2\x3e\x56\x8a\x5b\x31\xf1\x47\x76\x05\xb5\xc5\xae\x1c\x81\x4b\x2b\x56\x69\x44\xcb\x83\x12\x16\x6d\x26\x99\x3a\x62\xb8\x72\x0a\xb4\x16\x9d\x5a\xbc\x52\xba\xfc\x61\x88\x87\xa3\xbf\x99\x1d\x71\x97\x4d\xbe\x5f\xe6\x53\xce\x3e\x5d\x4f\x3e\x5d\x3b\xd3\xdd\x27\x7c\x24\x21\xa3\xde\xc9\xdf\xce\xd4\xe2\x9f\x9c\x27\x23\xa8\x70\xf4\xf7\x27\xa7\x6d\x79\x32\x82\x02\x47\x7f\xdb\x11\x3b\xc9\x12\x99\xc9\xea\xa6\x39\x9b\x9d\x51\xb3\xa4\x61\xe5\xee\x27\x8b\x29\x58\xbc\xf9\xfb\x53\x69\x35\x9f\x4a\xeb\xc9\x68\xf1\xc0\xfb\xba\xaf\xa3\xb0\x8c\x6a\xbf\xee\xaf\x8f\x24\x18\x4f\x3c\x43\x09\x6e\xa1\x2f\x45\x63\xce\x73\xa7\x44\x59\x9e\xcd\xce\x58\xac\xe3\x48\xdf\x0d\xe3\xc8\xf6\x7c\xaf\xbf\xf2\x18\x92\x16\x8a\x31\xee\x01\x09\xd8\x38\x7c\xda\x72\x75\x16\x0f\x8d\xef\x06\x22\xab\xb0\xba\x77\xad\x15\x79\xcf\x7c\xe3\x92\x3c\xef\x68\xec\x3f\x87\xc4\x34\x93\x21\xa6\x91\xf0\xb3\x5b\x4e\x6f\x2c\xc5\x04\xb6\xd7\xc8\x34\xb2\xfd\x7b\x05\x86\xeb\x50\x0b\x87\x7a\x88\xf1\x3d\x75\x19\x43\xca\x83\x2f\xfa\x8a\xd2\x50\x4e\xbc\x61\xb1\x24\x32\x06\x97\xb3\x52\x0c\x0c\x2b\xf1\x0d\x83\x93\x7f\xdf\xe6\x71\x6b\x0e\xb4\x71\xda\xef\x6d\xee\xc4\x98\xb7\x09\x17\x78\x8b\xae\x3a\xdd\x0f\xce\xec\xb2\xcc\xd3\xba\x12\xca\x07\x44\xf5\xfe\xf0\xc4\xdb\x7b\xb8\xa5\x2c\xef\xdf\x03\x30\xe1\x94\x24\x86\xe2\x16\x3e\x38\xb1\x90\xe9\x23\xd1\x40\x77\x1f\xa2\xe6\x03\xfd\x55\x49\xb4\x31\x57\x73\xf2\xd5\x7a\x56\x88\xf9\x87\x1c\x3f\x38\xf1\x6a\x8d\xdb\x34\xef\x41\xbc\x45\x0f\xa4\x02\xb0\x55\x58\xa1\xe6\xb7\xe9\x9b\x77\x2a\x6f\x8f\x1f\x9c\xf9\xfa\xb1\x9c\x44\xa1\x4a\x3b\x5a\xa3\x54\xf4\x44\xad\xd3\x54\xbb\xe9\x8c\x65\x58\x74\x77\x8b\x1e\xd9\x07\x8d\xe6\xe8\x86\xf3\xdd\x1b\xc8\x90\xc2\x23\xed\xc3\x65\x3b\x9e\x8b\xe8\x06\x99\x92\x2e\x41\x32\xda\x82\x73\x43\xa1\xa2\x4c\xb7\x25\xc7\x5c\x5e\xc9\xb9\x98\xff\x76\x83\xea\xf9\x57\x3b\xdb\x83\x57\xf7\x77\x06\xef\xe0\x2b\xdf\x02\xa1\xd2\xee\x62\x21\x8a\x0e\x96\x6a\xf8\x15\xc0\xfd\x47\x00\xba\xe0\x29\x80\xe2\x5b\x3d\x4b\x89\x4e\xe2\xdb\xaf\xa6\x3f\x05\xd2\x6a\x8f\x53\x3b\x49\xf3\xbc\xf8\xe7\x47\xbc\xa7\x26\x2d\x0a\x31\xab\x44\xf1\x61\x39\xcb\x90\xa2\xc1\x5f\x2d\xfc\xec\x91\x23\x0e\xdd\x7b\x10\xce\x8b\x23\xda\x82\x62\x97\x45\x25\x7e\x05\xeb\x80\xac\x08\xb2\xec\x91\x7d\x70\x1d\xf9\x67\x04\x58\x96\xc7\xa4\x87\xc4\xc3\x2d\x0d\x87\x9a\x63\xf4\xa8\x96\xfc\xd8\x3e\xff\x7a\xb8\x69\x6e\xb1\x4e\xa8\xdb\x3a\xbe\x1a\x6b\x58\x67\xb3\xb3\x47\xe6\xab\xa1\x65\x3b\x42\x2c\x66\x95\xbc\x12\xd8\xbe\x3c\x42\x70\x3d\xfc\x85\xab\x27\xfc\xb7\x28\xf2\xff\x09\x27\x17\x5b\xfe\x9f\xb8\x53\x9a\x91\x8a\xb2\x6c\x8f\x23\xfd\xe5\x71\x3c\x7f\xe4\x38\xf4\x82\xdd\xf4\xed\xb3\x48\x7f\x7d\x16\x87\xca\xde\xfe\xef\x87\xa1\x6e\x8e\xf0\x83\x53\xd6\x97\xf7\x40\xdd\x8d\x18\x14\x8c\x04\x4b\x47\xd5\x6a\xbd\x55\x62\x88\x5b\xbc\x9e\xa9\x5a\x9e\x61\xd2\x34\xc3\xec\xae\xfe\x54\x8e\x23\x19\xcd\xe1\xa6\xc0\x8a\x14\x98\x9d\x41\xe9\xac\xd3\xba\x64\x82\x07\xca\xaa\xa0\x3a\x41\x50\x39\xea\xd1\x0d\x2c\xb1\x74\x62\x58\xa0\x68\x55\x48\xda\x34\x43\x7d\xd1\x3a\x5c\x36\xcd\x70\xd1\x01\x5b\x46\xac\x85\x27\xb8\xaf\xd7\x5c\x44\xa5\xdf\xad\x3b\x5c\x6a\x57\x76\xab\xba\x60\x40\xcf\x0f\x67\xd1\xc0\xa8\xf4\xf7\x10\xbf\x46\xb6\xeb\xbb\xca\xd6\xa7\x58\xb1\x94\x2b\x3f\x56\xdd\x49\x2f\x7b\xbf\x2e\xc1\xd4\x8e\xb5\x1b\xc0\x6a\x74\xc3\x84\x47\x2c\x41\x3b\x81\x1c\x97\xdc\x67\x31\xa6\x90\xe3\x82\xac\x41\x21\xae\x44\x41\xb6\x0a\x32\x4c\xd4\x05\x6f\xbe\xb9\x03\xda\xea\xbe\xdd\x0a\x6a\x58\x8d\x2c\xe9\x6f\xad\xf9\x0b\x96\xf5\x77\xfb\x9c\x47\x89\x9f\x41\x82\x19\xba\x81\x0c\xb3\x20\xd3\x81\xcf\x72\x92\x4d\x87\xb8\x20\xad\xf9\xb3\x46\x7a\x7b\x41\x2f\x9b\xcb\x04\x0a\x7d\x73\x24\xaf\x78\x01\x0b\xcc\x41\x11\x40\x38\x25\xe1\xc5\xe4\x06\xbe\xad\x52\x15\x9d\xdf\xdb\xdd\x54\xeb\x9b\xe9\x49\xd1\xba\xb8\xd4\x94\xe1\x99\xed\x05\x32\x4c\xf4\xf5\xc8\x52\x5d\xb1\xbe\x58\xa8\xd0\x4b\x17\x5a\xc9\xa0\x30\xcd\x21\x75\x14\x53\x9a\x3c\xc5\x8c\x07\xb6\x4d\x4f\xb0\x9c\xc8\xa9\x85\x67\xb7\xf4\x6b\x23\xcd\x52\x77\x19\x14\x2a\xd3\x51\x04\xcb\x3e\x52\xb6\xed\xb8\xd7\xf8\xea\x94\x4e\x98\x80\x25\xc4\xdc\x57\x87\xa8\x4f\xcc\xf3\x3d\xd8\xba\xcc\x00\xa1\x14\xe1\x2a\x9f\xd7\x29\x09\xcb\x2a\x9f\x3f\xc2\xe1\xfa\xd6\x5c\xd5\x20\x6e\xcc\x9e\x77\x97\xb7\x87\xd2\x89\x9b\x66\x28\x9c\xb2\x69\x04\x89\xf6\x50\x17\x2e\x44\x1b\x06\xf7\xa9\xa9\x69\xa4\xea\x95\xdb\xbd\x92\xfb\xec\x10\xf1\xcf\x88\x15\x4a\x44\x94\xed\x86\x0a\x5f\x31\x09\x02\x5c\xd8\xe3\xaa\xa9\x80\xca\x29\x77\xb1\xe0\xfe\xa6\xeb\x4f\x0e\x52\x0b\x28\xab\x1c\x75\x51\xcb\x04\xd7\x36\x21\x23\x6d\x25\xe6\xa8\x9e\xfe\xa9\xef\xa0\xce\x5a\xfb\xbb\xda\x58\x92\xf4\x91\xfb\x31\x7f\x8c\x32\x1d\x5d\x20\xa7\x78\xb3\x95\xfa\xf1\xa3\x52\x9f\xff\x5a\xea\xf3\x87\x52\xdf\xed\xa9\x15\xfb\x1a\x55\x7c\xa8\xab\x40\x46\x37\x90\xa8\x70\x36\xed\xc5\xbe\x6e\x9a\x61\xa9\xc5\x9e\xb4\x4b\x7a\x77\x9d\xbc\x93\xf2\x44\x4b\x79\xba\x25\xe5\xf4\x4c\x6e\xa0\x1a\x48\xfd\x91\xf4\xdd\xdd\x5c\x89\x75\x8d\x15\xab\x39\x29\x36\x56\x92\x28\x27\xbd\x58\xe7\x58\xdb\x6d\xde\x2c\x0f\xdd\x88\x95\x58\x43\x81\x29\xf7\x59\x8e\x76\x0e\x05\x26\x1c\x8a\x8d\xcc\x06\xb9\x6d\x07\xc5\x46\x9c\xb7\xba\xda\x9b\xb9\xa4\x0b\x77\x32\x4c\xbb\x47\x37\xcc\xed\x4c\xd5\xdd\xa5\x40\xee\x69\x82\x05\x64\x98\xd3\xea\x6e\x90\x05\x3c\x47\x96\x4c\x6c\x3b\x9b\x62\x32\xc9\xa6\x56\x4a\x7f\x72\x3e\x3a\x6b\x5c\xa0\x86\x1d\x3c\xeb\xce\x35\x37\x4d\x96\xf4\x21\x57\xce\xc1\xb2\x4a\x0e\x24\x1f\x09\x94\x8a\x57\xfa\x3a\x00\x52\xf3\xdb\x27\xad\xcf\x59\x65\x3d\xf4\x49\x4b\x2c\x34\xd1\xfb\x0c\xaa\x18\xaa\xf4\xbd\x69\x7a\x43\xa4\x77\x57\xff\x30\x9d\x7f\xdb\x03\xa3\xcb\x39\x1b\x2a\x05\x0f\x62\xa8\x87\xb7\x59\x58\x4e\xc2\x73\xdf\xf3\xab\x50\xf6\x5e\x1f\x64\x58\xed\xde\x58\x24\x10\x72\x52\xb5\x5a\x23\xa8\x5a\x77\xaf\x52\xee\x5e\x46\xee\x9e\x4e\x63\x4a\x52\x0b\x95\x0a\xb4\xda\x3e\x0a\xb4\xfa\x5b\x4b\xd3\x2c\xc8\x05\x0a\x89\xb2\xe4\x5b\x0a\xcb\xe3\xa0\xcc\x9c\x2a\x7b\x78\x4c\xfc\x1f\x11\x15\xa6\x2b\x91\x44\xd3\xf4\xf9\xe3\xa7\x9c\x9b\xe6\x47\x56\xc1\xbf\xff\x2d\xac\xde\xd3\xba\x53\x60\xec\xc2\x73\xf0\x9e\xea\xca\xa7\xcc\xff\xca\xa1\xa2\x75\xd5\xa9\x3c\x24\xf9\x1d\x85\xa3\x6e\x75\x2e\xe0\x02\xbc\x67\x5b\xf4\xe4\x51\xd6\xca\xbc\xe1\x09\xc3\x52\xb5\x33\x2d\x2b\x67\xa4\x65\x32\xa5\x64\x4c\x93\xd9\x17\xba\x68\xe6\x82\x66\x94\xbb\xea\x1e\xc8\xf5\x3d\x52\x4a\x99\x3a\xff\xf2\x5b\x3d\x2b\xc4\xfb\x3c\xaf\x88\x01\xbe\x15\xd5\x63\xce\xfa\x03\x3b\x4f\x22\x58\x3a\x25\x45\x7a\xaa\x90\xea\x9d\xb5\x0f\x8b\x96\x5a\x86\xeb\x3c\xd5\xc1\x1e\xb1\x05\xd9\x65\x92\xcc\x64\x4b\xf4\xf4\x38\x32\xd9\xae\x0a\xeb\x69\x80\xea\x8f\xdc\x91\xeb\x27\x51\xa9\x10\x0c\x94\x7d\x55\xa9\x7f\xc2\x8b\x11\xe7\xba\x0a\x60\x8a\xe8\x8d\xdc\x88\x4e\x91\x25\x1c\x58\x57\xc6\x63\xc5\x7c\x67\x8c\xaa\x8a\x31\xd3\x35\x52\xb0\x0d\x20\xd3\x86\x9a\xc5\x96\xc7\x47\x63\x6e\x33\x37\x8c\x9b\x26\xde\x19\xd3\x30\x05\x31\x43\x4d\x4e\x9f\x91\x34\xde\x29\x75\x51\xe6\x39\xdb\xd4\x64\x6f\x2a\x2c\x85\xc1\x2d\x8f\x5b\x31\x07\xd9\x52\x20\xe3\xdc\xef\x9e\x53\xcb\x30\x48\x53\xd3\x79\x28\x43\xa9\xb2\x61\x90\x62\x6c\x2d\x61\x4f\x6d\x3f\x25\x83\x19\xe8\xfa\x57\x09\x64\x69\xf5\xd1\xd6\xda\x01\x7a\xc5\x4a\xa8\x61\x09\x9e\xba\x9c\x63\xb5\x13\xf3\x1e\x8d\x94\x6b\x37\xae\x60\xd2\x89\xf9\x76\xbb\xd2\x89\xd2\x11\x2f\x62\xd3\xb4\xed\x74\x0b\xf9\xd4\xde\x83\x94\x78\xdf\x38\x3c\x3c\x3c\x34\x14\x8f\xb2\xbc\x69\x8c\xfd\xf6\x95\xf3\x9f\x6c\x68\x65\x4d\x33\xb4\xb2\xbe\x10\xd9\x34\x8d\xa7\x06\x62\xd6\x55\x06\xba\xc4\xf4\xec\x23\x93\x20\x1d\x61\xbd\xb3\xc6\x40\x31\x27\x0e\x65\x8b\xbc\xe4\x8e\xf8\xc6\xca\xed\x6a\x85\x61\xae\x66\xd4\x50\xb7\x33\x5c\x0e\x75\xb7\xd7\x6e\x38\xff\x29\xb1\x6e\xe7\x2c\x2d\xdc\x87\x94\xfe\xe4\xe8\xdd\xf6\x81\x4d\xb7\xa4\x07\x5f\x5b\x33\xae\x60\x90\x15\xaf\xd3\xff\xc9\x4f\x6d\xeb\x80\xba\x04\xea\x4a\xa7\x50\x35\x57\x9f\xe3\xa5\x13\xc3\x05\x92\x1d\x3b\xb8\x63\xc7\x78\x97\x39\x3d\x37\xcd\x0b\x9d\x41\x32\xcd\x8b\xad\xcc\xe9\xf0\x92\x0c\xa7\xf6\x00\xce\x4d\x73\xa8\x47\x0c\x2f\x9a\xe6\x82\x7e\xf4\xdb\x79\x5f\x5f\x21\xda\xf8\x5f\x79\x27\xbb\x78\xe9\x94\x40\x90\x23\x5d\x6b\xe1\xea\xfa\x15\x97\xfb\xdb\xf5\x18\x1c\x44\x5b\x92\x56\xb1\x4b\x15\xc9\x58\x15\x13\x3a\x61\xda\x43\x49\x37\xb9\xb3\x05\x5e\xf4\x8f\x8a\xc7\x56\x78\x0e\xe7\x78\x01\x17\xb8\x82\x5c\x99\x15\xe5\xe4\x91\x49\x49\xad\x05\xac\x70\x32\x55\xb6\x6a\xb5\x55\x7e\x94\x17\xec\x1a\xcf\xe0\x0a\x5f\x92\xab\x1a\xd8\x76\x1e\xa2\x1b\x6c\x8a\xe4\xd7\x78\x31\xc9\xa7\x3b\x57\x30\x57\x0f\xa3\xab\xc6\x85\x12\x53\xa8\x31\xb7\xca\xa0\x0e\xf3\x80\xc7\x78\xae\xee\x4d\x76\xae\x60\x89\xe7\x93\x52\x0f\x4a\x70\xbe\x1b\x5b\xcb\xdd\x35\xc4\xb8\xde\x8d\xad\x64\xe7\x6a\xf7\xca\x5a\x4d\xea\xa9\x55\x40\x81\x2c\x1e\x5d\xab\x1b\x82\x84\x46\x73\x6b\xbe\xbb\x84\xd5\xa4\xb6\xed\x29\xc6\x3b\xd7\x01\x8d\xc3\xa2\x63\x87\x22\xb2\x2c\xe9\xaf\x7a\x67\x90\x6c\xdb\x0a\xa4\x66\x8b\xb6\x6c\xed\x1f\xaa\xf6\xc1\xbd\xcb\x41\x8f\x94\xfb\xf3\xed\x52\x39\x7d\x51\xa8\x5c\xa4\x0c\x1f\x2a\xf8\xe7\xbd\x82\x07\x11\x91\x41\xa0\xe5\xfc\x4a\xa3\xb2\xa5\x4b\x1e\x0f\xcb\x3e\xb7\xa1\xd8\x83\xfb\xc9\x43\x1e\x91\x65\xf1\xda\x85\xa9\x41\x83\x54\x95\x77\xff\x37\x60\x63\x57\x03\xeb\xcc\x54\x07\x73\xec\x76\x30\x55\x0d\xdf\xa3\x14\xfb\x25\x4c\xef\x17\x30\x3d\xa5\xc3\x75\x9c\xbb\xe5\x36\x3a\xe5\x3a\x95\x95\x2e\x4d\xcf\xd1\xfa\xcb\xe9\x0b\x79\xa0\xa6\xd7\x87\xb5\x3c\x50\x62\x37\xaa\xab\xe2\x21\x4f\x90\x84\x25\x45\x39\x51\x25\xda\x5d\xfc\x0d\x33\x8c\xa3\xa4\xd7\x5b\x7e\x02\xcb\x4d\xf9\x53\x1b\xe6\x14\x98\x93\x27\x07\x35\x16\xb0\xb4\xb1\xe0\x90\x87\xae\x69\x2e\x43\xb7\xe3\xee\xe5\x4e\xde\x34\x39\x24\x38\x6b\xbf\x89\x60\x2e\x14\x3c\x58\x86\x45\x50\x58\x98\xf3\xc4\xc2\xd2\xea\xfb\x0a\xc8\x79\x50\x87\xaa\x7c\xbe\xed\x50\xcb\x17\x9c\x43\xac\x6a\xea\x0d\xdb\xb0\x12\x7e\x5b\x61\x1a\x25\xd6\x5f\xce\xfd\x12\x27\x8b\x82\x44\xeb\x2f\xe7\x41\x59\x12\x8f\xd2\x4d\x66\x72\xeb\x4b\xa1\x4f\x9f\xe6\x3f\x0d\xab\xb6\x8c\xdb\x4f\x9f\x7e\x33\xc0\x58\x18\x1c\x8c\x27\xa6\xf1\x00\x46\xb7\x02\xf7\x53\xee\x27\x9b\xc2\x5c\x7d\xd8\xed\xd0\x47\xdd\xbe\x7b\x4a\x13\xbf\xc0\x42\xab\xca\x35\x2e\x9c\x18\xe6\xfd\xbd\x3a\xac\xb0\xda\xbc\x5c\x63\x72\xe7\xc6\xbd\x67\x17\xf6\x05\x87\x1e\x94\xd8\x97\x62\x7f\xc1\x25\xb0\x21\xa3\x48\x5e\xe5\x70\x18\xe7\x4d\x53\x3a\x69\xc5\xbe\x29\xe3\xa2\xcb\x23\xc6\x60\xac\x66\xdf\x07\x73\x91\xe5\x2b\x99\xd1\x56\x06\x86\xc5\x96\x91\x71\xaf\x06\xf8\xb1\x12\x60\x81\xc3\xa5\x69\xaa\x84\xcb\x47\x56\x82\x76\xcc\x3c\xee\x2c\x2a\xc1\xbe\xf1\xa8\xf4\x3b\x37\x74\xdd\xc7\xfe\xdb\x65\xe8\xda\x5c\x17\x6c\x4d\x7c\x3a\x77\x04\xf6\x89\xa3\x85\x23\x6c\x0f\xe6\xca\xaa\xe3\xfb\x09\xab\x31\xdf\xb9\xe1\x2f\xdc\xe8\xc6\xaa\xfd\x7a\x4a\x0b\x0b\xda\x4b\xbc\x5a\xb3\x39\x0f\xdd\x88\x82\x85\xb9\xbf\xf2\x4b\xa8\xf1\x07\xfc\x20\x6f\xa3\x27\x45\xcc\x21\xd1\x90\xdc\x20\x45\x32\xf7\x73\x95\x1d\x54\xb2\xa2\x5c\x80\xb4\xb5\x92\xd7\x9c\x83\x37\xa4\x10\x68\xb5\xa6\x08\x89\x57\x78\x0d\xd7\x28\x61\x85\xc9\xdd\x91\x12\x57\x9c\x22\x17\x09\x73\x2c\xdb\x90\x6a\xd3\x37\xe7\x14\xdc\xc8\x4e\xef\x49\x7c\xc5\x44\x17\x4b\x72\xb8\xd6\xab\x27\x1d\xcc\xce\xa4\x13\xc4\xaa\x43\x49\x6e\xa1\x94\x38\x25\xae\x9c\x12\x17\x4e\x09\xf9\x2e\x8e\x21\xc3\x57\x8c\xac\x6b\x0e\x5f\x79\x0b\x77\xc1\x9d\xd9\x65\xc9\xb8\x42\xfd\x15\x4b\xa0\x7a\xac\x97\xbf\xf0\xa2\xc9\x6a\xeb\x0c\xe0\x7a\xeb\x65\xea\x4f\x92\xed\xbe\x6a\xbb\x0f\x7e\x60\xad\xdd\xf9\x2a\xd7\x35\xc2\x0f\x23\xdf\x2d\xc7\xda\x12\x4d\x43\x06\x38\x72\x77\x85\xa3\xf3\x41\x7a\xee\xbb\xfc\x5a\xa5\x15\xd7\xf9\xf5\x2f\xa2\xa1\x55\x57\x4d\x65\x09\xde\xa5\x07\xc8\x41\xe8\x5d\xf5\xf1\x1e\x18\xa2\x55\xf7\xaa\xfe\x67\xd8\x65\x35\x99\xe0\x4d\x53\x84\x17\x14\x03\x8d\xd0\xe5\x4d\xb3\x9e\x15\xa5\x38\x4e\xf3\x59\xc5\x04\x57\x72\x32\x64\x02\x09\x9d\x7b\x37\x0d\xca\x8f\x5d\xe7\xd7\xcc\x92\x20\x78\x97\x61\xf9\x3d\x9a\xb3\xdf\x47\x37\xd6\x98\xfb\x2e\x6c\xa4\xb0\xad\x48\x2d\x76\xc6\xea\x57\x5d\x8b\xb4\x6e\x19\x0c\x2b\x27\x6e\x2b\x45\x33\xd3\xac\xfa\x6c\xa8\x0a\x8c\x36\xaf\x98\x71\x5d\x1e\xbc\x62\xc5\x68\xcc\xa1\x2b\x5a\x0e\x24\x6e\x7c\x3c\xc8\x4c\x53\xa5\x35\xe4\x5d\x30\xf2\x0e\x98\x3b\xd9\xf8\x0a\xbf\x39\x73\x79\xc5\x2a\xce\x21\x53\x56\xf2\x77\xf8\xda\x5b\xc9\xbe\x48\xfc\x9f\x9b\x35\x55\x15\xb7\xff\x2b\x33\x0d\xe3\xfd\xf6\x60\x35\xa7\x3c\x76\xa6\x5d\x7c\x5b\x11\xff\x62\xe5\x88\x60\x2b\x28\x45\xc4\x3c\x92\x14\x6c\x18\xdd\x1d\x99\x01\x6e\x28\x55\x14\x49\x6a\x9d\xbc\xfd\x0c\x8d\xb3\xd9\x99\xe1\x2b\x57\x9c\xe8\xdb\xfb\x07\x2d\x92\xea\x0b\xd3\xf1\xd3\xee\x13\xd3\xe8\x35\x4b\x59\x06\x39\x07\xb7\x11\xe0\xb9\x20\xb9\xff\x5b\x88\x64\x73\x42\x7c\x12\x25\xaa\xcf\xef\x86\xd0\x62\x55\x17\xd1\xf5\x8b\xb6\xcc\x5e\xd4\x59\xdc\x66\x7b\xd4\xf3\x3f\xbf\x0b\xd0\xf7\x0f\x57\xb3\xb4\x16\xe7\x09\x4d\xcf\x7f\xbf\x38\x7f\x24\x13\xae\x53\xdb\x1b\x51\xbb\xdd\xd0\xbf\xab\x3a\x25\x75\x3e\xdb\xd4\x4b\x54\x9b\x58\xd6\x6d\x7a\x6a\x8a\xd0\x6d\x1a\x81\x88\x59\x94\xf9\x99\xed\xdd\xa9\xaf\xd8\x54\x56\x68\x21\xf3\x40\x6e\x8a\x50\x72\xf5\x9d\x8a\x65\x18\x81\x0c\x8b\xd6\x03\xcd\x50\xa8\x6c\xa3\x65\x18\x50\xe1\x8d\xdd\x7f\xcb\x51\xd9\x76\x90\x51\xf4\x67\x65\x3c\xc8\x2d\xcc\x6e\xdb\x42\x90\x3b\x5f\x25\xe6\x77\xbf\x4a\x94\x3c\xe8\xdd\xc0\x7c\xf3\xbd\x9f\xe5\x35\x8d\xc7\x37\x88\xca\xfb\xb9\x41\xe1\xc4\x90\x53\x54\xa4\xbe\x29\x2a\x49\xa7\x3b\xa5\xaa\x9f\xa1\x18\x2f\x73\xc4\x56\x96\xea\x61\xa6\xc3\x34\x87\xca\x89\x29\x30\x37\xcd\x61\xae\x8a\xba\x9a\xa6\xbf\x0d\xab\xa2\x22\x72\x7d\xbb\xf4\x6b\xe5\xb8\x0c\xb1\x87\x51\x6b\x00\x6e\x58\x43\x81\x09\x62\x0a\x43\xd9\x34\xc3\x9c\xf7\x5e\xb1\xeb\x0f\xe5\xdf\x95\x2e\x6b\xb9\x73\xc5\x96\x84\x69\xd7\xae\x8b\x8b\x58\xd2\xa7\x5c\xf8\x0b\x96\xf6\x74\xe2\x51\xe2\x93\x33\xef\x06\x65\x58\x07\xb5\xce\x22\xcb\x49\x3d\x1d\x62\x3e\xa9\xfb\x60\x9e\x5a\x42\x6a\xe8\xa0\xf6\x9f\x49\x63\x1a\xb9\xfe\x66\xb9\x0d\x15\xf3\xbb\xb7\xb7\x4c\xe8\x8f\x7f\x42\x72\xa6\xab\x10\xb7\xaa\x7d\x6a\x62\x8c\xf6\xa3\xbf\x89\x2e\x8e\x1c\xa8\x52\xb8\xa9\x81\x78\xae\xde\x37\xe5\xe7\x3d\x8b\xea\xef\x91\xc4\xd6\xb9\x95\x0f\xbe\xff\x21\xf7\x46\x45\x5b\xb5\x2a\x94\xef\xbf\x77\xa2\xbd\xb6\xdf\x80\x6e\x38\x46\xda\x76\x90\x4f\xe4\x74\x17\xb3\xb6\x1e\x6c\x52\xa0\x3b\xb5\xf0\xbc\x4f\x03\x88\x2e\x30\x26\x42\xf1\xa0\x78\xd1\x4f\x2e\x2c\x8b\xe7\x93\x62\x1a\x56\xea\x6b\x5d\xad\x53\xf2\x49\x61\x79\x24\xce\xfa\x01\x5d\x0e\xfa\xc9\xa2\xae\xe9\xa8\x6a\x5c\x6a\x98\xee\x60\xd5\xeb\xcf\xed\xbb\x80\x7e\x67\xc9\xb6\x7e\x64\x9b\xaa\xa2\x48\x6c\x22\x75\xcb\x70\x0c\x4b\x6c\x5c\x62\xc1\x2d\xe6\x86\x59\x64\x90\xdf\x24\x2c\x83\x5b\xd9\x06\x60\x7a\x87\xc5\x75\xd9\x5a\xd6\xb9\xc5\x86\xeb\x18\x81\x65\x65\xe4\x04\xab\x6f\xd0\x04\x16\x96\xe8\x0b\x0c\xab\x8d\xc8\x5a\x56\x16\x56\x9b\x69\x06\x64\x36\x56\x81\x6d\x6f\x4d\xb5\xb0\xd0\x33\x2b\x65\x33\x36\x75\x65\xfa\x93\xf7\x2d\x9c\x33\xbe\x89\xd1\x36\x98\xc6\x1b\xe6\x18\x08\xbc\x63\x48\x81\x2c\xf4\x9c\x09\xee\xaf\x88\x0f\x68\x33\x33\x1d\xf7\xeb\x6a\x87\x4f\x73\x8b\x7d\x72\x3e\xcd\x77\x79\xd4\xd0\xaf\xc5\x99\x98\x58\xf6\x34\xa2\xc7\xe8\xc9\x88\xdc\x26\x65\x70\x63\x21\x53\x58\xe9\x67\x75\xd5\x0a\xd7\xd8\x56\xeb\x0e\x2e\xf3\x3c\x15\xb3\x6c\x90\x17\x83\x4b\x99\xcd\x8a\x9b\xc1\x9c\xc2\x4d\x03\xae\x50\x7f\x49\x25\xb3\xc5\x60\x95\xcf\x85\x01\x97\xdd\x87\xe9\x03\x62\xd4\xc1\x72\x56\x0e\x56\x79\x21\x06\xd5\x72\x96\x0d\xbc\xa7\x83\x52\x2e\x32\x99\xc8\x78\x96\x55\x1a\x48\x69\xc0\x39\x1a\xae\x37\xde\xdb\x7f\xfa\xec\xe0\xf9\xe1\xec\x32\x9e\x8b\x64\xb1\x94\x5f\xbe\xa6\xab\x2c\x5f\x7f\x2b\xca\xaa\xbe\xba\xfe\x7e\xf3\xe3\xe5\x6f\xaf\x5e\x1f\x1d\xbf\xf9\xd7\xc9\xef\xff\xdf\xe9\xdb\xb3\xf3\x77\xff\xff\xfb\x8b\x0f\x1f\xff\xf8\xf3\xaf\xff\xfa\xef\x27\x9f\x0d\x38\x43\x4f\x78\xfb\x70\x83\xde\x3e\x5c\xdc\x2f\xec\xf5\xe0\x3d\x4e\x3c\x32\x3f\x9e\xeb\x82\x27\xf6\xc0\x13\xfb\xe0\x89\xa7\xe0\x89\x67\xe0\x89\x03\xf0\xc4\x73\xf0\xc4\x21\x78\x82\x06\x09\xcf\xa3\x3f\x63\xfa\xb3\x37\x85\x97\xea\x43\x8e\x23\xf4\xc4\xa1\xfa\xa2\x4a\x55\x51\x1a\xdd\xf1\x6c\x8a\x9d\xe7\x22\x91\x99\x30\x4d\xfd\xeb\xcc\x56\x73\xae\x1f\xd9\x43\x53\x33\xbb\xdd\x7c\xb7\x69\xd4\x99\x1e\x37\xdf\x54\x7f\xab\x0b\x1b\x61\x9a\xfa\xd7\x21\x2f\xab\xa8\xf4\x05\xc0\xdd\x26\x9c\xc1\x70\xc9\xab\xe2\xe6\xe7\x12\x0b\xf1\xad\x96\x85\x60\x6d\x3d\xa8\xc1\x6f\xe3\x59\x15\x2f\xd9\x6b\xfe\xf3\x56\x73\xa0\x70\xfa\x2f\xcb\x70\x76\xdb\x66\x05\xfe\x63\x34\xfa\xcf\x41\x99\xd7\x45\x2c\xde\xce\xd6\x6b\x99\x2d\x3e\xbe\x3f\xc5\x79\x1e\xdf\xf9\xf7\x1a\xce\x6a\xb6\xfe\x8f\xff\x17\x00\x00\xff\xff\x2f\x88\x72\xca\xa2\x43\x00\x00") + +func bignumberJsBytes() ([]byte, error) { + return bindataRead( + _bignumberJs, + "bignumber.js", + ) +} + +func bignumberJs() (*asset, error) { + bytes, err := bignumberJsBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bignumber.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "bignumber.js": bignumberJs, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "bignumber.js": {bignumberJs, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/signer/rules/deps/deps.go b/signer/rules/deps/deps.go new file mode 100644 index 0000000000..5ee00b900a --- /dev/null +++ b/signer/rules/deps/deps.go @@ -0,0 +1,21 @@ +// Copyright 2018 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 . + +// Package deps contains the console JavaScript dependencies Go embedded. +package deps + +//go:generate go-bindata -nometadata -pkg deps -o bindata.go bignumber.js +//go:generate gofmt -w -s bindata.go diff --git a/signer/rules/rules.go b/signer/rules/rules.go new file mode 100644 index 0000000000..711e2dddec --- /dev/null +++ b/signer/rules/rules.go @@ -0,0 +1,248 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package rules + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/signer/core" + "github.com/ethereum/go-ethereum/signer/rules/deps" + "github.com/ethereum/go-ethereum/signer/storage" + "github.com/robertkrimen/otto" +) + +var ( + BigNumber_JS = deps.MustAsset("bignumber.js") +) + +// consoleOutput is an override for the console.log and console.error methods to +// stream the output into the configured output stream instead of stdout. +func consoleOutput(call otto.FunctionCall) otto.Value { + output := []string{"JS:> "} + for _, argument := range call.ArgumentList { + output = append(output, fmt.Sprintf("%v", argument)) + } + fmt.Fprintln(os.Stdout, strings.Join(output, " ")) + return otto.Value{} +} + +// rulesetUI provides an implementation of SignerUI that evaluates a javascript +// file for each defined UI-method +type rulesetUI struct { + next core.SignerUI // The next handler, for manual processing + storage storage.Storage + credentials storage.Storage + jsRules string // The rules to use +} + +func NewRuleEvaluator(next core.SignerUI, jsbackend, credentialsBackend storage.Storage) (*rulesetUI, error) { + c := &rulesetUI{ + next: next, + storage: jsbackend, + credentials: credentialsBackend, + jsRules: "", + } + + return c, nil +} + +func (r *rulesetUI) Init(javascriptRules string) error { + r.jsRules = javascriptRules + return nil +} +func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (otto.Value, error) { + + // Instantiate a fresh vm engine every time + vm := otto.New() + // Set the native callbacks + consoleObj, _ := vm.Get("console") + consoleObj.Object().Set("log", consoleOutput) + consoleObj.Object().Set("error", consoleOutput) + vm.Set("storage", r.storage) + + // Load bootstrap libraries + script, err := vm.Compile("bignumber.js", BigNumber_JS) + if err != nil { + log.Warn("Failed loading libraries", "err", err) + return otto.UndefinedValue(), err + } + vm.Run(script) + + // Run the actual rule implementation + _, err = vm.Run(r.jsRules) + if err != nil { + log.Warn("Execution failed", "err", err) + return otto.UndefinedValue(), err + } + + // And the actual call + // All calls are objects with the parameters being keys in that object. + // To provide additional insulation between js and go, we serialize it into JSON on the Go-side, + // and deserialize it on the JS side. + + jsonbytes, err := json.Marshal(jsarg) + if err != nil { + log.Warn("failed marshalling data", "data", jsarg) + return otto.UndefinedValue(), err + } + // Now, we call foobar(JSON.parse()). + var call string + if len(jsonbytes) > 0 { + call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes)) + } else { + call = fmt.Sprintf("%v()", jsfunc) + } + return vm.Run(call) +} + +func (r *rulesetUI) checkApproval(jsfunc string, jsarg []byte, err error) (bool, error) { + if err != nil { + return false, err + } + v, err := r.execute(jsfunc, string(jsarg)) + if err != nil { + log.Info("error occurred during execution", "error", err) + return false, err + } + result, err := v.ToString() + if err != nil { + log.Info("error occurred during response unmarshalling", "error", err) + return false, err + } + if result == "Approve" { + log.Info("Op approved") + return true, nil + } else if result == "Reject" { + log.Info("Op rejected") + return false, nil + } + return false, fmt.Errorf("Unknown response") +} + +func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveTx", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveTx(request) + } + + if approved { + return core.SignTxResponse{ + Transaction: request.Transaction, + Approved: true, + Password: r.lookupPassword(request.Transaction.From.Address()), + }, + nil + } + return core.SignTxResponse{Approved: false}, err +} + +func (r *rulesetUI) lookupPassword(address common.Address) string { + return r.credentials.Get(strings.ToLower(address.String())) +} + +func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveSignData", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveSignData(request) + } + if approved { + return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil + } + return core.SignDataResponse{Approved: false, Password: ""}, err +} + +func (r *rulesetUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveExport", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveExport(request) + } + if approved { + return core.ExportResponse{Approved: true}, nil + } + return core.ExportResponse{Approved: false}, err +} + +func (r *rulesetUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + // This cannot be handled by rules, requires setting a password + // dispatch to next + return r.next.ApproveImport(request) +} + +func (r *rulesetUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveListing", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveListing(request) + } + if approved { + return core.ListResponse{Accounts: request.Accounts}, nil + } + return core.ListResponse{}, err +} + +func (r *rulesetUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + // This cannot be handled by rules, requires setting a password + // dispatch to next + return r.next.ApproveNewAccount(request) +} + +func (r *rulesetUI) ShowError(message string) { + log.Error(message) + r.next.ShowError(message) +} + +func (r *rulesetUI) ShowInfo(message string) { + log.Info(message) + r.next.ShowInfo(message) +} +func (r *rulesetUI) OnSignerStartup(info core.StartupInfo) { + jsonInfo, err := json.Marshal(info) + if err != nil { + log.Warn("failed marshalling data", "data", info) + return + } + r.next.OnSignerStartup(info) + _, err = r.execute("OnSignerStartup", string(jsonInfo)) + if err != nil { + log.Info("error occurred during execution", "error", err) + } +} + +func (r *rulesetUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + jsonTx, err := json.Marshal(tx) + if err != nil { + log.Warn("failed marshalling transaction", "tx", tx) + return + } + _, err = r.execute("OnApprovedTx", string(jsonTx)) + if err != nil { + log.Info("error occurred during execution", "error", err) + } +} diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go new file mode 100644 index 0000000000..b6060eba73 --- /dev/null +++ b/signer/rules/rules_test.go @@ -0,0 +1,631 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +// +package rules + +import ( + "fmt" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/signer/core" + "github.com/ethereum/go-ethereum/signer/storage" +) + +const JS = ` +/** +This is an example implementation of a Javascript rule file. + +When the signer receives a request over the external API, the corresponding method is evaluated. +Three things can happen: + +1. The method returns "Approve". This means the operation is permitted. +2. The method returns "Reject". This means the operation is rejected. +3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means +that the operation will continue to manual processing, via the regular UI method chosen by the user. + +[*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not +only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all +accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject"). + +**/ + +function ApproveListing(request){ + console.log("In js approve listing"); + console.log(request.accounts[3].Address) + console.log(request.meta.Remote) + return "Approve" +} + +function ApproveTx(request){ + console.log("test"); + console.log("from"); + return "Reject"; +} + +function test(thing){ + console.log(thing.String()) +} + +` + +func mixAddr(a string) (*common.MixedcaseAddress, error) { + return common.NewMixedcaseAddressFromString(a) +} + +type alwaysDenyUI struct{} + +func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) { +} + +func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil +} + +func (alwaysDenyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + return core.SignDataResponse{Approved: false, Password: ""}, nil +} + +func (alwaysDenyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + return core.ExportResponse{Approved: false}, nil +} + +func (alwaysDenyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil +} + +func (alwaysDenyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + return core.ListResponse{Accounts: nil}, nil +} + +func (alwaysDenyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + return core.NewAccountResponse{Approved: false, Password: ""}, nil +} + +func (alwaysDenyUI) ShowError(message string) { + panic("implement me") +} + +func (alwaysDenyUI) ShowInfo(message string) { + panic("implement me") +} + +func (alwaysDenyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + panic("implement me") +} + +func initRuleEngine(js string) (*rulesetUI, error) { + r, err := NewRuleEvaluator(&alwaysDenyUI{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) + if err != nil { + return nil, fmt.Errorf("failed to create js engine: %v", err) + } + if err = r.Init(js); err != nil { + return nil, fmt.Errorf("failed to load bootstrap js: %v", err) + } + return r, nil +} + +func TestListRequest(t *testing.T) { + accs := make([]core.Account, 5) + + for i := range accs { + addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i) + acc := core.Account{ + Address: common.BytesToAddress(common.Hex2Bytes(addr)), + URL: accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)}, + } + accs[i] = acc + } + + js := `function ApproveListing(){ return "Approve" }` + + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + resp, err := r.ApproveListing(&core.ListRequest{ + Accounts: accs, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + }) + if len(resp.Accounts) != len(accs) { + t.Errorf("Expected check to resolve to 'Approve'") + } +} + +func TestSignTxRequest(t *testing.T) { + + js := ` + function ApproveTx(r){ + console.log("transaction.from", r.transaction.from); + console.log("transaction.to", r.transaction.to); + console.log("transaction.value", r.transaction.value); + console.log("transaction.nonce", r.transaction.nonce); + if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} + if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} + }` + + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + to, err := mixAddr("000000000000000000000000000000000000dead") + if err != nil { + t.Error(err) + return + } + from, err := mixAddr("0000000000000000000000000000000000001337") + + if err != nil { + t.Error(err) + return + } + fmt.Printf("to %v", to.Address().String()) + resp, err := r.ApproveTx(&core.SignTxRequest{ + Transaction: core.SendTxArgs{ + From: *from, + To: to}, + Callinfo: nil, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + }) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if !resp.Approved { + t.Errorf("Expected check to resolve to 'Approve'") + } +} + +type dummyUI struct { + calls []string +} + +func (d *dummyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + d.calls = append(d.calls, "ApproveTx") + return core.SignTxResponse{}, core.ErrRequestDenied +} + +func (d *dummyUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + d.calls = append(d.calls, "ApproveSignData") + return core.SignDataResponse{}, core.ErrRequestDenied +} + +func (d *dummyUI) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + d.calls = append(d.calls, "ApproveExport") + return core.ExportResponse{}, core.ErrRequestDenied +} + +func (d *dummyUI) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + d.calls = append(d.calls, "ApproveImport") + return core.ImportResponse{}, core.ErrRequestDenied +} + +func (d *dummyUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + d.calls = append(d.calls, "ApproveListing") + return core.ListResponse{}, core.ErrRequestDenied +} + +func (d *dummyUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + d.calls = append(d.calls, "ApproveNewAccount") + return core.NewAccountResponse{}, core.ErrRequestDenied +} + +func (d *dummyUI) ShowError(message string) { + d.calls = append(d.calls, "ShowError") +} + +func (d *dummyUI) ShowInfo(message string) { + d.calls = append(d.calls, "ShowInfo") +} + +func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + d.calls = append(d.calls, "OnApprovedTx") +} +func (d *dummyUI) OnSignerStartup(info core.StartupInfo) { +} + +//TestForwarding tests that the rule-engine correctly dispatches requests to the next caller +func TestForwarding(t *testing.T) { + + js := "" + ui := &dummyUI{make([]string, 0)} + jsBackend := storage.NewEphemeralStorage() + credBackend := storage.NewEphemeralStorage() + r, err := NewRuleEvaluator(ui, jsBackend, credBackend) + if err != nil { + t.Fatalf("Failed to create js engine: %v", err) + } + if err = r.Init(js); err != nil { + t.Fatalf("Failed to load bootstrap js: %v", err) + } + r.ApproveSignData(nil) + r.ApproveTx(nil) + r.ApproveImport(nil) + r.ApproveNewAccount(nil) + r.ApproveListing(nil) + r.ApproveExport(nil) + r.ShowError("test") + r.ShowInfo("test") + + //This one is not forwarded + r.OnApprovedTx(ethapi.SignTransactionResult{}) + + expCalls := 8 + if len(ui.calls) != expCalls { + + t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ",")) + + } + +} + +func TestMissingFunc(t *testing.T) { + r, err := initRuleEngine(JS) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + + _, err = r.execute("MissingMethod", "test") + + if err == nil { + t.Error("Expected error") + } + + approved, err := r.checkApproval("MissingMethod", nil, nil) + if err == nil { + t.Errorf("Expected missing method to yield error'") + } + if approved { + t.Errorf("Expected missing method to cause non-approval") + } + fmt.Printf("Err %v", err) + +} +func TestStorage(t *testing.T) { + + js := ` + function testStorage(){ + storage.Put("mykey", "myvalue") + a = storage.Get("mykey") + + storage.Put("mykey", ["a", "list"]) // Should result in "a,list" + a += storage.Get("mykey") + + + storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]" + a += storage.Get("mykey") + + + storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}' + a += storage.Get("mykey") + + a += storage.Get("missingkey") //Missing keys should result in empty string + storage.Put("","missing key==noop") // Can't store with 0-length key + a += storage.Get("") // Should result in '' + + var b = new BigNumber(2) + var c = new BigNumber(16)//"0xf0",16) + var d = b.plus(c) + console.log(d) + return a + } +` + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + + v, err := r.execute("testStorage", nil) + + if err != nil { + t.Errorf("Unexpected error %v", err) + } + + retval, err := v.ToString() + + if err != nil { + t.Errorf("Unexpected error %v", err) + } + exp := `myvaluea,list[object Object]{"an":"object"}` + if retval != exp { + t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval) + } + fmt.Printf("Err %v", err) + +} + +const ExampleTxWindow = ` + function big(str){ + if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)} + return new BigNumber(str) + } + + // Time window: 1 week + var window = 1000* 3600*24*7; + + // Limit : 1 ether + var limit = new BigNumber("1e18"); + + function isLimitOk(transaction){ + var value = big(transaction.value) + // Start of our window function + var windowstart = new Date().getTime() - window; + + var txs = []; + var stored = storage.Get('txs'); + + if(stored != ""){ + txs = JSON.parse(stored) + } + // First, remove all that have passed out of the time-window + var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart}); + console.log(txs, newtxs.length); + + // Secondly, aggregate the current sum + sum = new BigNumber(0) + + sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum); + console.log("ApproveTx > Sum so far", sum); + console.log("ApproveTx > Requested", value.toNumber()); + + // Would we exceed weekly limit ? + return sum.plus(value).lt(limit) + + } + function ApproveTx(r){ + console.log(r) + console.log(typeof(r)) + if (isLimitOk(r.transaction)){ + return "Approve" + } + return "Nope" + } + + /** + * OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter + * 'response_str' contains the return value that will be sent to the external caller. + * The return value from this method is ignore - the reason for having this callback is to allow the + * ruleset to keep track of approved transactions. + * + * When implementing rate-limited rules, this callback should be used. + * If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user + * then accepts the transaction, this method will be called. + * + * TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx. + */ + function OnApprovedTx(resp){ + var value = big(resp.tx.value) + var txs = [] + // Load stored transactions + var stored = storage.Get('txs'); + if(stored != ""){ + txs = JSON.parse(stored) + } + // Add this to the storage + txs.push({tstamp: new Date().getTime(), value: value}); + storage.Put("txs", JSON.stringify(txs)); + } + +` + +func dummyTx(value hexutil.Big) *core.SignTxRequest { + + to, _ := mixAddr("000000000000000000000000000000000000dead") + from, _ := mixAddr("000000000000000000000000000000000000dead") + n := hexutil.Uint64(3) + gas := hexutil.Uint64(21000) + gasPrice := hexutil.Big(*big.NewInt(2000000)) + + return &core.SignTxRequest{ + Transaction: core.SendTxArgs{ + From: *from, + To: to, + Value: value, + Nonce: n, + GasPrice: gasPrice, + Gas: gas, + }, + Callinfo: []core.ValidationInfo{ + {Typ: "Warning", Message: "All your base are bellong to us"}, + }, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + } +} +func dummyTxWithV(value uint64) *core.SignTxRequest { + + v := big.NewInt(0).SetUint64(value) + h := hexutil.Big(*v) + return dummyTx(h) +} +func dummySigned(value *big.Int) *types.Transaction { + to := common.HexToAddress("000000000000000000000000000000000000dead") + gas := uint64(21000) + gasPrice := big.NewInt(2000000) + data := make([]byte, 0) + return types.NewTransaction(3, to, value, gas, gasPrice, data) + +} +func TestLimitWindow(t *testing.T) { + + r, err := initRuleEngine(ExampleTxWindow) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + + // 0.3 ether: 429D069189E0000 wei + v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000")) + h := hexutil.Big(*v) + // The first three should succeed + for i := 0; i < 3; i++ { + unsigned := dummyTx(h) + resp, err := r.ApproveTx(unsigned) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if !resp.Approved { + t.Errorf("Expected check to resolve to 'Approve'") + } + // Create a dummy signed transaction + + response := ethapi.SignTransactionResult{ + Tx: dummySigned(v), + Raw: common.Hex2Bytes("deadbeef"), + } + r.OnApprovedTx(response) + } + // Fourth should fail + resp, err := r.ApproveTx(dummyTx(h)) + if resp.Approved { + t.Errorf("Expected check to resolve to 'Reject'") + } + +} + +// dontCallMe is used as a next-handler that does not want to be called - it invokes test failure +type dontCallMe struct { + t *testing.T +} + +func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) { +} + +func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.SignTxResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.SignDataResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.ExportResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.ImportResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.ListResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.NewAccountResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ShowError(message string) { + d.t.Fatalf("Did not expect next-handler to be called") +} + +func (d *dontCallMe) ShowInfo(message string) { + d.t.Fatalf("Did not expect next-handler to be called") +} + +func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) { + d.t.Fatalf("Did not expect next-handler to be called") +} + +//TestContextIsCleared tests that the rule-engine does not retain variables over several requests. +// if it does, that would be bad since developers may rely on that to store data, +// instead of using the disk-based data storage +func TestContextIsCleared(t *testing.T) { + + js := ` + function ApproveTx(){ + if (typeof foobar == 'undefined') { + foobar = "Approve" + } + console.log(foobar) + if (foobar == "Approve"){ + foobar = "Reject" + }else{ + foobar = "Approve" + } + return foobar + } + ` + ui := &dontCallMe{t} + r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) + if err != nil { + t.Fatalf("Failed to create js engine: %v", err) + } + if err = r.Init(js); err != nil { + t.Fatalf("Failed to load bootstrap js: %v", err) + } + tx := dummyTxWithV(0) + r1, err := r.ApproveTx(tx) + r2, err := r.ApproveTx(tx) + if r1.Approved != r2.Approved { + t.Errorf("Expected execution context to be cleared between executions") + } +} + +func TestSignData(t *testing.T) { + + js := `function ApproveListing(){ + return "Approve" +} +function ApproveSignData(r){ + if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa") + { + if(r.message.indexOf("bazonk") >= 0){ + return "Approve" + } + return "Reject" + } + // Otherwise goes to manual processing +}` + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + message := []byte("baz bazonk foo") + hash, msg := core.SignHash(message) + raw := hexutil.Bytes(message) + addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") + + fmt.Printf("address %v %v\n", addr.String(), addr.Original()) + resp, err := r.ApproveSignData(&core.SignDataRequest{ + Address: *addr, + Message: msg, + Hash: hash, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + Rawdata: raw, + }) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if !resp.Approved { + t.Fatalf("Expected approved") + } +} diff --git a/signer/storage/aes_gcm_storage.go b/signer/storage/aes_gcm_storage.go new file mode 100644 index 0000000000..225276667f --- /dev/null +++ b/signer/storage/aes_gcm_storage.go @@ -0,0 +1,163 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +// + +package storage + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/json" + "io" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/log" +) + +type storedCredential struct { + // The iv + Iv []byte `json:"iv"` + // The ciphertext + CipherText []byte `json:"c"` +} + +// AESEncryptedStorage is a storage type which is backed by a json-faile. The json-file contains +// key-value mappings, where the keys are _not_ encrypted, only the values are. +type AESEncryptedStorage struct { + // File to read/write credentials + filename string + // Key stored in base64 + key []byte +} + +// NewAESEncryptedStorage creates a new encrypted storage backed by the given file/key +func NewAESEncryptedStorage(filename string, key []byte) *AESEncryptedStorage { + return &AESEncryptedStorage{ + filename: filename, + key: key, + } +} + +// Put stores a value by key. 0-length keys results in no-op +func (s *AESEncryptedStorage) Put(key, value string) { + if len(key) == 0 { + return + } + data, err := s.readEncryptedStorage() + if err != nil { + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + return + } + ciphertext, iv, err := encrypt(s.key, []byte(value)) + if err != nil { + log.Warn("Failed to encrypt entry", "err", err) + return + } + encrypted := storedCredential{Iv: iv, CipherText: ciphertext} + data[key] = encrypted + if err = s.writeEncryptedStorage(data); err != nil { + log.Warn("Failed to write entry", "err", err) + } +} + +// Get returns the previously stored value, or the empty string if it does not exist or key is of 0-length +func (s *AESEncryptedStorage) Get(key string) string { + if len(key) == 0 { + return "" + } + data, err := s.readEncryptedStorage() + if err != nil { + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + return "" + } + encrypted, exist := data[key] + if !exist { + log.Warn("Key does not exist", "key", key) + return "" + } + entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText) + if err != nil { + log.Warn("Failed to decrypt key", "key", key) + return "" + } + return string(entry) +} + +// readEncryptedStorage reads the file with encrypted creds +func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) { + creds := make(map[string]storedCredential) + raw, err := ioutil.ReadFile(s.filename) + + if err != nil { + if os.IsNotExist(err) { + // Doesn't exist yet + return creds, nil + } + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + } + if err = json.Unmarshal(raw, &creds); err != nil { + log.Warn("Failed to unmarshal encrypted storage", "err", err, "file", s.filename) + return nil, err + } + return creds, nil +} + +// writeEncryptedStorage write the file with encrypted creds +func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCredential) error { + raw, err := json.Marshal(creds) + if err != nil { + return err + } + if err = ioutil.WriteFile(s.filename, raw, 0600); err != nil { + return err + } + return nil +} + +func encrypt(key []byte, plaintext []byte) ([]byte, []byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + aesgcm, err := cipher.NewGCM(block) + nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, nil, err + } + if err != nil { + return nil, nil, err + } + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + return ciphertext, nonce, nil +} + +func decrypt(key []byte, nonce []byte, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + return plaintext, nil +} diff --git a/signer/storage/aes_gcm_storage_test.go b/signer/storage/aes_gcm_storage_test.go new file mode 100644 index 0000000000..77804905ac --- /dev/null +++ b/signer/storage/aes_gcm_storage_test.go @@ -0,0 +1,115 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +// +package storage + +import ( + "bytes" + "fmt" + "io/ioutil" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/mattn/go-colorable" +) + +func TestEncryption(t *testing.T) { + // key := []byte("AES256Key-32Characters1234567890") + // plaintext := []byte(value) + key := []byte("AES256Key-32Characters1234567890") + plaintext := []byte("exampleplaintext") + + c, iv, err := encrypt(key, plaintext) + if err != nil { + t.Fatal(err) + } + fmt.Printf("Ciphertext %x, nonce %x\n", c, iv) + + p, err := decrypt(key, iv, c) + if err != nil { + t.Fatal(err) + } + fmt.Printf("Plaintext %v\n", string(p)) + if !bytes.Equal(plaintext, p) { + t.Errorf("Failed: expected plaintext recovery, got %v expected %v", string(plaintext), string(p)) + } +} + +func TestFileStorage(t *testing.T) { + + a := map[string]storedCredential{ + "secret": { + Iv: common.Hex2Bytes("cdb30036279601aeee60f16b"), + CipherText: common.Hex2Bytes("f311ac49859d7260c2c464c28ffac122daf6be801d3cfd3edcbde7e00c9ff74f"), + }, + "secret2": { + Iv: common.Hex2Bytes("afb8a7579bf971db9f8ceeed"), + CipherText: common.Hex2Bytes("2df87baf86b5073ef1f03e3cc738de75b511400f5465bb0ddeacf47ae4dc267d"), + }, + } + d, err := ioutil.TempDir("", "eth-encrypted-storage-test") + if err != nil { + t.Fatal(err) + } + stored := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + stored.writeEncryptedStorage(a) + read := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + creds, err := read.readEncryptedStorage() + if err != nil { + t.Fatal(err) + } + for k, v := range a { + if v2, exist := creds[k]; !exist { + t.Errorf("Missing entry %v", k) + } else { + if !bytes.Equal(v.CipherText, v2.CipherText) { + t.Errorf("Wrong ciphertext, expected %x got %x", v.CipherText, v2.CipherText) + } + if !bytes.Equal(v.Iv, v2.Iv) { + t.Errorf("Wrong iv") + } + } + } +} +func TestEnd2End(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + + d, err := ioutil.TempDir("", "eth-encrypted-storage-test") + if err != nil { + t.Fatal(err) + } + + s1 := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + s2 := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + + s1.Put("bazonk", "foobar") + if v := s2.Get("bazonk"); v != "foobar" { + t.Errorf("Expected bazonk->foobar, got '%v'", v) + } +} diff --git a/signer/storage/storage.go b/signer/storage/storage.go new file mode 100644 index 0000000000..60f4e3892a --- /dev/null +++ b/signer/storage/storage.go @@ -0,0 +1,62 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +// + +package storage + +import ( + "fmt" +) + +type Storage interface { + // Put stores a value by key. 0-length keys results in no-op + Put(key, value string) + // Get returns the previously stored value, or the empty string if it does not exist or key is of 0-length + Get(key string) string +} + +// EphemeralStorage is an in-memory storage that does +// not persist values to disk. Mainly used for testing +type EphemeralStorage struct { + data map[string]string + namespace string +} + +func (s *EphemeralStorage) Put(key, value string) { + if len(key) == 0 { + return + } + fmt.Printf("storage: put %v -> %v\n", key, value) + s.data[key] = value +} + +func (s *EphemeralStorage) Get(key string) string { + if len(key) == 0 { + return "" + } + fmt.Printf("storage: get %v\n", key) + if v, exist := s.data[key]; exist { + return v + } + return "" +} + +func NewEphemeralStorage() Storage { + s := &EphemeralStorage{ + data: make(map[string]string), + } + return s +} diff --git a/swarm/fuse/fuse_dir.go b/swarm/fuse/fuse_dir.go index 91b236ae8a..a7701985eb 100644 --- a/swarm/fuse/fuse_dir.go +++ b/swarm/fuse/fuse_dir.go @@ -19,12 +19,13 @@ package fuse import ( - "bazil.org/fuse" - "bazil.org/fuse/fs" - "golang.org/x/net/context" "os" "path/filepath" "sync" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "golang.org/x/net/context" ) var ( diff --git a/swarm/fuse/swarmfs.go b/swarm/fuse/swarmfs.go index 2493bdab19..e56d0ad4e3 100644 --- a/swarm/fuse/swarmfs.go +++ b/swarm/fuse/swarmfs.go @@ -17,9 +17,10 @@ package fuse import ( - "github.com/ethereum/go-ethereum/swarm/api" "sync" "time" + + "github.com/ethereum/go-ethereum/swarm/api" ) const ( diff --git a/swarm/network/kademlia/address.go b/swarm/network/kademlia/address.go index 4c38a846f9..ef82d2e8b8 100644 --- a/swarm/network/kademlia/address.go +++ b/swarm/network/kademlia/address.go @@ -51,7 +51,7 @@ func (a Address) Bin() string { /* Proximity(x, y) returns the proximity order of the MSB distance between x and y -The distance metric MSB(x, y) of two equal length byte sequences x an y is the +The distance metric MSB(x, y) of two equal length byte sequences x and y is the value of the binary integer cast of the x^y, ie., x and y bitwise xor-ed. the binary cast is big endian: most significant bit first (=MSB). diff --git a/swarm/storage/database.go b/swarm/storage/database.go index 2532490cc9..f2ceb94e46 100644 --- a/swarm/storage/database.go +++ b/swarm/storage/database.go @@ -22,7 +22,6 @@ package storage import ( "fmt" - "github.com/ethereum/go-ethereum/compression/rle" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" @@ -31,8 +30,7 @@ import ( const openFileLimit = 128 type LDBDatabase struct { - db *leveldb.DB - comp bool + db *leveldb.DB } func NewLDBDatabase(file string) (*LDBDatabase, error) { @@ -42,16 +40,12 @@ func NewLDBDatabase(file string) (*LDBDatabase, error) { return nil, err } - database := &LDBDatabase{db: db, comp: false} + database := &LDBDatabase{db: db} return database, nil } func (self *LDBDatabase) Put(key []byte, value []byte) { - if self.comp { - value = rle.Compress(value) - } - err := self.db.Put(key, value, nil) if err != nil { fmt.Println("Error put", err) @@ -63,11 +57,6 @@ func (self *LDBDatabase) Get(key []byte) ([]byte, error) { if err != nil { return nil, err } - - if self.comp { - return rle.Decompress(dat) - } - return dat, nil } diff --git a/swarm/storage/dbstore.go b/swarm/storage/dbstore.go index 421bb061d5..1ff42a0c05 100644 --- a/swarm/storage/dbstore.go +++ b/swarm/storage/dbstore.go @@ -54,7 +54,6 @@ const ( // key prefixes for leveldb storage kpIndex = 0 - kpData = 1 ) var ( diff --git a/swarm/storage/netstore.go b/swarm/storage/netstore.go index 5d4f17deb1..0552b84efa 100644 --- a/swarm/storage/netstore.go +++ b/swarm/storage/netstore.go @@ -83,11 +83,6 @@ func NewNetStore(hash SwarmHasher, lstore *LocalStore, cloud CloudStore, params } } -const ( - // maximum number of peers that a retrieved message is delivered to - requesterCount = 3 -) - var ( // timeout interval before retrieval is timed out searchTimeout = 3 * time.Second diff --git a/tests/block_test_util.go b/tests/block_test_util.go index beba484833..a72799f6e2 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -98,13 +98,13 @@ func (t *BlockTest) Run() error { } // import pre accounts & construct test genesis block & state root - db, _ := ethdb.NewMemDatabase() + db := ethdb.NewMemDatabase() gblock, err := t.genesis(config).Commit(db) if err != nil { return err } if gblock.Hash() != t.json.Genesis.Hash { - return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x\n", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) + return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) } if gblock.Root() != t.json.Genesis.StateRoot { return fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6]) diff --git a/tests/init.go b/tests/init.go index ff8ee7da18..0bea5ccd63 100644 --- a/tests/init.go +++ b/tests/init.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// This table defines supported forks and their chain config. +// Forks table defines supported forks and their chain config. var Forks = map[string]*params.ChainConfig{ "Frontier": { ChainId: big.NewInt(1), diff --git a/tests/init_test.go b/tests/init_test.go index fbb214b08c..26e919d24b 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -42,7 +42,7 @@ var ( difficultyTestDir = filepath.Join(baseDir, "BasicTests") ) -func readJson(reader io.Reader, value interface{}) error { +func readJSON(reader io.Reader, value interface{}) error { data, err := ioutil.ReadAll(reader) if err != nil { return fmt.Errorf("error reading JSON file: %v", err) @@ -57,14 +57,14 @@ func readJson(reader io.Reader, value interface{}) error { return nil } -func readJsonFile(fn string, value interface{}) error { +func readJSONFile(fn string, value interface{}) error { file, err := os.Open(fn) if err != nil { return err } defer file.Close() - err = readJson(file, value) + err = readJSON(file, value) if err != nil { return fmt.Errorf("%s in file %s", err.Error(), fn) } @@ -169,9 +169,8 @@ func (tm *testMatcher) checkFailure(t *testing.T, name string, err error) error if err != nil { t.Logf("error: %v", err) return nil - } else { - return fmt.Errorf("test succeeded unexpectedly") } + return fmt.Errorf("test succeeded unexpectedly") } return err } @@ -213,7 +212,7 @@ func (tm *testMatcher) runTestFile(t *testing.T, path, name string, runTest inte // Load the file as map[string]. m := makeMapFromTestFunc(runTest) - if err := readJsonFile(path, m.Addr().Interface()); err != nil { + if err := readJSONFile(path, m.Addr().Interface()); err != nil { t.Fatal(err) } diff --git a/tests/state_test.go b/tests/state_test.go index 9ca5f18303..adec4feb2b 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -36,14 +36,7 @@ func TestState(t *testing.T) { st.skipLoad(`^stTransactionTest/zeroSigTransa[^/]*\.json`) // EIP-86 is not supported yet // Expected failures: st.fails(`^stRevertTest/RevertPrecompiledTouch\.json/EIP158`, "bug in test") - st.fails(`^stRevertTest/RevertPrefoundEmptyOOG\.json/EIP158`, "bug in test") st.fails(`^stRevertTest/RevertPrecompiledTouch\.json/Byzantium`, "bug in test") - st.fails(`^stRevertTest/RevertPrefoundEmptyOOG\.json/Byzantium`, "bug in test") - st.fails(`^stRandom2/randomStatetest64[45]\.json/(EIP150|Frontier|Homestead)/.*`, "known bug #15119") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/EIP158/2`, "known bug ") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/EIP158/3`, "known bug ") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/Byzantium/2`, "known bug ") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/Byzantium/3`, "known bug ") st.walk(t, stateTestDir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 3b761bd771..84581fae18 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -126,8 +126,7 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD return nil, UnsupportedForkError{subtest.Fork} } block := t.genesis(config).ToBlock(nil) - db, _ := ethdb.NewMemDatabase() - statedb := MakePreState(db, t.json.Pre) + statedb := MakePreState(ethdb.NewMemDatabase(), t.json.Pre) post := t.json.Post[subtest.Fork][subtest.Index] msg, err := t.json.Tx.toMessage(post) diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 2028d2a278..8c3dac088c 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -72,9 +72,8 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { if err := rlp.DecodeBytes(tt.json.RLP, tx); err != nil { if tt.json.Transaction == nil { return nil - } else { - return fmt.Errorf("RLP decoding failed: %v", err) } + return fmt.Errorf("RLP decoding failed: %v", err) } // Check sender derivation. signer := types.MakeSigner(config, new(big.Int).SetUint64(uint64(tt.json.BlockNumber))) diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index b365167a69..cb81c5b94e 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -79,8 +79,7 @@ type vmExecMarshaling struct { } func (t *VMTest) Run(vmconfig vm.Config) error { - db, _ := ethdb.NewMemDatabase() - statedb := MakePreState(db, t.json.Pre) + statedb := MakePreState(ethdb.NewMemDatabase(), t.json.Pre) ret, gasRemaining, err := t.exec(statedb, vmconfig) if t.json.GasRemaining == nil { diff --git a/trie/iterator.go b/trie/iterator.go index 76146c0d64..3bae8e186b 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -303,7 +303,7 @@ func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path [] it.path = path it.stack = append(it.stack, state) if parentIndex != nil { - *parentIndex += 1 + *parentIndex++ } } @@ -380,7 +380,7 @@ func (it *differenceIterator) Next(bool) bool { if !it.b.Next(true) { return false } - it.count += 1 + it.count++ if it.eof { // a has reached eof, so we just return all elements from b @@ -395,7 +395,7 @@ func (it *differenceIterator) Next(bool) bool { it.eof = true return true } - it.count += 1 + it.count++ case 1: // b is before a return true @@ -405,12 +405,12 @@ func (it *differenceIterator) Next(bool) bool { if !it.b.Next(hasHash) { return false } - it.count += 1 + it.count++ if !it.a.Next(hasHash) { it.eof = true return true } - it.count += 1 + it.count++ } } } @@ -504,14 +504,14 @@ func (it *unionIterator) Next(descend bool) bool { skipped := heap.Pop(it.items).(NodeIterator) // Skip the whole subtree if the nodes have hashes; otherwise just skip this node if skipped.Next(skipped.Hash() == common.Hash{}) { - it.count += 1 + it.count++ // If there are more elements, push the iterator back on the heap heap.Push(it.items, skipped) } } if least.Next(descend) { - it.count += 1 + it.count++ heap.Push(it.items, least) } diff --git a/trie/iterator_test.go b/trie/iterator_test.go index dce1c78b5d..2a510b1c2d 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -289,7 +289,7 @@ func TestIteratorContinueAfterErrorDisk(t *testing.T) { testIteratorContinueA func TestIteratorContinueAfterErrorMemonly(t *testing.T) { testIteratorContinueAfterError(t, true) } func testIteratorContinueAfterError(t *testing.T, memonly bool) { - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) tr, _ := New(common.Hash{}, triedb) @@ -376,7 +376,7 @@ func TestIteratorContinueAfterSeekErrorMemonly(t *testing.T) { func testIteratorContinueAfterSeekError(t *testing.T, memonly bool) { // Commit test trie to db, then remove the node containing "bars". - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) ctr, _ := New(common.Hash{}, triedb) diff --git a/trie/node.go b/trie/node.go index a7697fc0c6..02815042c6 100644 --- a/trie/node.go +++ b/trie/node.go @@ -123,17 +123,17 @@ func decodeNode(hash, buf []byte, cachegen uint16) (node, error) { } switch c, _ := rlp.CountValues(elems); c { case 2: - n, err := decodeShort(hash, buf, elems, cachegen) + n, err := decodeShort(hash, elems, cachegen) return n, wrapError(err, "short") case 17: - n, err := decodeFull(hash, buf, elems, cachegen) + n, err := decodeFull(hash, elems, cachegen) return n, wrapError(err, "full") default: return nil, fmt.Errorf("invalid number of list elements: %v", c) } } -func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { +func decodeShort(hash, elems []byte, cachegen uint16) (node, error) { kbuf, rest, err := rlp.SplitString(elems) if err != nil { return nil, err @@ -155,7 +155,7 @@ func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { return &shortNode{key, r, flag}, nil } -func decodeFull(hash, buf, elems []byte, cachegen uint16) (*fullNode, error) { +func decodeFull(hash, elems []byte, cachegen uint16) (*fullNode, error) { n := &fullNode{flags: nodeFlag{hash: hash, gen: cachegen}} for i := 0; i < 16; i++ { cld, rest, err := decodeRef(elems, cachegen) diff --git a/trie/proof_test.go b/trie/proof_test.go index fff313d7fd..a3537787cc 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -36,7 +36,7 @@ func TestProof(t *testing.T) { trie, vals := randomTrie(500) root := trie.Hash() for _, kv := range vals { - proofs, _ := ethdb.NewMemDatabase() + proofs := ethdb.NewMemDatabase() if trie.Prove(kv.k, 0, proofs) != nil { t.Fatalf("missing key %x while constructing proof", kv.k) } @@ -53,7 +53,7 @@ func TestProof(t *testing.T) { func TestOneElementProof(t *testing.T) { trie := new(Trie) updateString(trie, "k", "v") - proofs, _ := ethdb.NewMemDatabase() + proofs := ethdb.NewMemDatabase() trie.Prove([]byte("k"), 0, proofs) if len(proofs.Keys()) != 1 { t.Error("proof should have one element") @@ -71,7 +71,7 @@ func TestVerifyBadProof(t *testing.T) { trie, vals := randomTrie(800) root := trie.Hash() for _, kv := range vals { - proofs, _ := ethdb.NewMemDatabase() + proofs := ethdb.NewMemDatabase() trie.Prove(kv.k, 0, proofs) if len(proofs.Keys()) == 0 { t.Fatal("zero length proof") @@ -109,7 +109,7 @@ func BenchmarkProve(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { kv := vals[keys[i%len(keys)]] - proofs, _ := ethdb.NewMemDatabase() + proofs := ethdb.NewMemDatabase() if trie.Prove(kv.k, 0, proofs); len(proofs.Keys()) == 0 { b.Fatalf("zero length proof for %x", kv.k) } @@ -123,7 +123,7 @@ func BenchmarkVerifyProof(b *testing.B) { var proofs []*ethdb.MemDatabase for k := range vals { keys = append(keys, k) - proof, _ := ethdb.NewMemDatabase() + proof := ethdb.NewMemDatabase() trie.Prove([]byte(k), 0, proof) proofs = append(proofs, proof) } diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index aedf5a1cde..d16d999684 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -28,18 +28,14 @@ import ( ) func newEmptySecure() *SecureTrie { - diskdb, _ := ethdb.NewMemDatabase() - triedb := NewDatabase(diskdb) - - trie, _ := NewSecure(common.Hash{}, triedb, 0) + trie, _ := NewSecure(common.Hash{}, NewDatabase(ethdb.NewMemDatabase()), 0) return trie } // makeTestSecureTrie creates a large enough secure trie for testing. func makeTestSecureTrie() (*Database, *SecureTrie, map[string][]byte) { // Create an empty trie - diskdb, _ := ethdb.NewMemDatabase() - triedb := NewDatabase(diskdb) + triedb := NewDatabase(ethdb.NewMemDatabase()) trie, _ := NewSecure(common.Hash{}, triedb, 0) diff --git a/trie/sync.go b/trie/sync.go index b573a9f732..4ae975d042 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -212,7 +212,7 @@ func (s *TrieSync) Process(results []SyncResult) (bool, int, error) { } // Commit flushes the data stored in the internal membatch out to persistent -// storage, returning th enumber of items written and any occurred error. +// storage, returning the number of items written and any occurred error. func (s *TrieSync) Commit(dbw ethdb.Putter) (int, error) { // Dump the membatch into a database dbw for i, key := range s.membatch.order { diff --git a/trie/sync_test.go b/trie/sync_test.go index 4a720612b6..142a6f5b1a 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -27,8 +27,7 @@ import ( // makeTestTrie create a sample test trie to test node-wise reconstruction. func makeTestTrie() (*Database, *Trie, map[string][]byte) { // Create an empty trie - diskdb, _ := ethdb.NewMemDatabase() - triedb := NewDatabase(diskdb) + triedb := NewDatabase(ethdb.NewMemDatabase()) trie, _ := New(common.Hash{}, triedb) // Fill it with some arbitrary data @@ -89,18 +88,13 @@ func checkTrieConsistency(db *Database, root common.Hash) error { // Tests that an empty trie is not scheduled for syncing. func TestEmptyTrieSync(t *testing.T) { - diskdbA, _ := ethdb.NewMemDatabase() - triedbA := NewDatabase(diskdbA) - - diskdbB, _ := ethdb.NewMemDatabase() - triedbB := NewDatabase(diskdbB) - - emptyA, _ := New(common.Hash{}, triedbA) - emptyB, _ := New(emptyRoot, triedbB) + dbA := NewDatabase(ethdb.NewMemDatabase()) + dbB := NewDatabase(ethdb.NewMemDatabase()) + emptyA, _ := New(common.Hash{}, dbA) + emptyB, _ := New(emptyRoot, dbB) for i, trie := range []*Trie{emptyA, emptyB} { - diskdb, _ := ethdb.NewMemDatabase() - if req := NewTrieSync(trie.Hash(), diskdb, nil).Missing(1); len(req) != 0 { + if req := NewTrieSync(trie.Hash(), ethdb.NewMemDatabase(), nil).Missing(1); len(req) != 0 { t.Errorf("test %d: content requested for empty trie: %v", i, req) } } @@ -116,7 +110,7 @@ func testIterativeTrieSync(t *testing.T, batch int) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) @@ -149,7 +143,7 @@ func TestIterativeDelayedTrieSync(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) @@ -187,7 +181,7 @@ func testIterativeRandomTrieSync(t *testing.T, batch int) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) @@ -228,7 +222,7 @@ func TestIterativeRandomDelayedTrieSync(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) @@ -275,7 +269,7 @@ func TestDuplicateAvoidanceTrieSync(t *testing.T) { srcDb, srcTrie, srcData := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) @@ -315,7 +309,7 @@ func TestIncompleteTrieSync(t *testing.T) { srcDb, srcTrie, _ := makeTestTrie() // Create a destination trie and sync with the scheduler - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) sched := NewTrieSync(srcTrie.Hash(), diskdb, nil) diff --git a/trie/trie_test.go b/trie/trie_test.go index 9972226288..f8e5fd12a1 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -43,8 +43,7 @@ func init() { // Used for testing func newEmpty() *Trie { - diskdb, _ := ethdb.NewMemDatabase() - trie, _ := New(common.Hash{}, NewDatabase(diskdb)) + trie, _ := New(common.Hash{}, NewDatabase(ethdb.NewMemDatabase())) return trie } @@ -68,8 +67,7 @@ func TestNull(t *testing.T) { } func TestMissingRoot(t *testing.T) { - diskdb, _ := ethdb.NewMemDatabase() - trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(diskdb)) + trie, err := New(common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"), NewDatabase(ethdb.NewMemDatabase())) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -82,7 +80,7 @@ func TestMissingNodeDisk(t *testing.T) { testMissingNode(t, false) } func TestMissingNodeMemonly(t *testing.T) { testMissingNode(t, true) } func testMissingNode(t *testing.T, memonly bool) { - diskdb, _ := ethdb.NewMemDatabase() + diskdb := ethdb.NewMemDatabase() triedb := NewDatabase(diskdb) trie, _ := New(common.Hash{}, triedb) @@ -413,8 +411,7 @@ func (randTest) Generate(r *rand.Rand, size int) reflect.Value { } func runRandTest(rt randTest) bool { - diskdb, _ := ethdb.NewMemDatabase() - triedb := NewDatabase(diskdb) + triedb := NewDatabase(ethdb.NewMemDatabase()) tr, _ := New(common.Hash{}, triedb) values := make(map[string]string) // tracks content of the trie diff --git a/vendor/github.com/elastic/gosigar/CHANGELOG.md b/vendor/github.com/elastic/gosigar/CHANGELOG.md index 12695e10ef..45262e7b8d 100644 --- a/vendor/github.com/elastic/gosigar/CHANGELOG.md +++ b/vendor/github.com/elastic/gosigar/CHANGELOG.md @@ -8,10 +8,21 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Added missing runtime import for FreeBSD. #104 + ### Changed ### Deprecated +## [0.9.0] + +### Added +- Added support for huge TLB pages on Linux #97 +- Added support for big endian platform #100 + +### Fixed +- Add missing method for OpenBSD #99 + ## [0.8.0] ### Added diff --git a/vendor/github.com/elastic/gosigar/README.md b/vendor/github.com/elastic/gosigar/README.md index 2482620a83..ecdfc1c3c5 100644 --- a/vendor/github.com/elastic/gosigar/README.md +++ b/vendor/github.com/elastic/gosigar/README.md @@ -26,6 +26,7 @@ The features vary by operating system. | FDUsage | X | | | | X | | FileSystemList | X | X | X | X | X | | FileSystemUsage | X | X | X | X | X | +| HugeTLBPages | X | | | | | | LoadAverage | X | X | | X | X | | Mem | X | X | X | X | X | | ProcArgs | X | X | X | | X | diff --git a/vendor/github.com/elastic/gosigar/concrete_sigar.go b/vendor/github.com/elastic/gosigar/concrete_sigar.go index 685aa6dedd..e3ee80a980 100644 --- a/vendor/github.com/elastic/gosigar/concrete_sigar.go +++ b/vendor/github.com/elastic/gosigar/concrete_sigar.go @@ -62,6 +62,12 @@ func (c *ConcreteSigar) GetSwap() (Swap, error) { return s, err } +func (c *ConcreteSigar) GetHugeTLBPages() (HugeTLBPages, error) { + p := HugeTLBPages{} + err := p.Get() + return p, err +} + func (c *ConcreteSigar) GetFileSystemUsage(path string) (FileSystemUsage, error) { f := FileSystemUsage{} err := f.Get(path) diff --git a/vendor/github.com/elastic/gosigar/sigar_darwin.go b/vendor/github.com/elastic/gosigar/sigar_darwin.go index f989f51608..a90b998c2e 100644 --- a/vendor/github.com/elastic/gosigar/sigar_darwin.go +++ b/vendor/github.com/elastic/gosigar/sigar_darwin.go @@ -91,6 +91,10 @@ func (self *Swap) Get() error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (self *Cpu) Get() error { var count C.mach_msg_type_number_t = C.HOST_CPU_LOAD_INFO_COUNT var cpuload C.host_cpu_load_info_data_t diff --git a/vendor/github.com/elastic/gosigar/sigar_freebsd.go b/vendor/github.com/elastic/gosigar/sigar_freebsd.go index 602b4a0aad..9b2af639b6 100644 --- a/vendor/github.com/elastic/gosigar/sigar_freebsd.go +++ b/vendor/github.com/elastic/gosigar/sigar_freebsd.go @@ -4,6 +4,7 @@ package gosigar import ( "io/ioutil" + "runtime" "strconv" "strings" "unsafe" @@ -97,6 +98,10 @@ func (self *ProcFDUsage) Get(pid int) error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func parseCpuStat(self *Cpu, line string) error { fields := strings.Fields(line) diff --git a/vendor/github.com/elastic/gosigar/sigar_interface.go b/vendor/github.com/elastic/gosigar/sigar_interface.go index a956af604a..df79ae08d2 100644 --- a/vendor/github.com/elastic/gosigar/sigar_interface.go +++ b/vendor/github.com/elastic/gosigar/sigar_interface.go @@ -26,6 +26,7 @@ type Sigar interface { GetLoadAverage() (LoadAverage, error) GetMem() (Mem, error) GetSwap() (Swap, error) + GetHugeTLBPages(HugeTLBPages, error) GetFileSystemUsage(string) (FileSystemUsage, error) GetFDUsage() (FDUsage, error) GetRusage(who int) (Rusage, error) @@ -82,6 +83,15 @@ type Swap struct { Free uint64 } +type HugeTLBPages struct { + Total uint64 + Free uint64 + Reserved uint64 + Surplus uint64 + DefaultSize uint64 + TotalAllocatedSize uint64 +} + type CpuList struct { List []Cpu } diff --git a/vendor/github.com/elastic/gosigar/sigar_linux.go b/vendor/github.com/elastic/gosigar/sigar_linux.go index cb1d3525b5..09f2e30b2f 100644 --- a/vendor/github.com/elastic/gosigar/sigar_linux.go +++ b/vendor/github.com/elastic/gosigar/sigar_linux.go @@ -45,6 +45,30 @@ func (self *FDUsage) Get() error { }) } +func (self *HugeTLBPages) Get() error { + table, err := parseMeminfo() + if err != nil { + return err + } + + self.Total, _ = table["HugePages_Total"] + self.Free, _ = table["HugePages_Free"] + self.Reserved, _ = table["HugePages_Rsvd"] + self.Surplus, _ = table["HugePages_Surp"] + self.DefaultSize, _ = table["Hugepagesize"] + + if totalSize, found := table["Hugetlb"]; found { + self.TotalAllocatedSize = totalSize + } else { + // If Hugetlb is not present, or huge pages of different sizes + // are used, this figure can be unaccurate. + // TODO (jsoriano): Extract information from /sys/kernel/mm/hugepages too + self.TotalAllocatedSize = (self.Total - self.Free + self.Reserved) * self.DefaultSize + } + + return nil +} + func (self *ProcFDUsage) Get(pid int) error { err := readFile(procFileName(pid, "limits"), func(line string) bool { if strings.HasPrefix(line, "Max open files") { diff --git a/vendor/github.com/elastic/gosigar/sigar_linux_common.go b/vendor/github.com/elastic/gosigar/sigar_linux_common.go index 8e5e7856f6..7ca6497622 100644 --- a/vendor/github.com/elastic/gosigar/sigar_linux_common.go +++ b/vendor/github.com/elastic/gosigar/sigar_linux_common.go @@ -379,12 +379,16 @@ func parseMeminfo() (map[string]uint64, error) { return true // skip on errors } - num := strings.TrimLeft(fields[1], " ") - val, err := strtoull(strings.Fields(num)[0]) + valueUnit := strings.Fields(fields[1]) + value, err := strtoull(valueUnit[0]) if err != nil { return true // skip on errors } - table[fields[0]] = val * 1024 //in bytes + + if len(valueUnit) > 1 && valueUnit[1] == "kB" { + value *= 1024 + } + table[fields[0]] = value return true }) @@ -420,8 +424,18 @@ func procFileName(pid int, name string) string { return Procd + "/" + strconv.Itoa(pid) + "/" + name } -func readProcFile(pid int, name string) ([]byte, error) { +func readProcFile(pid int, name string) (content []byte, err error) { path := procFileName(pid, name) + + // Panics have been reported when reading proc files, let's recover and + // report the path if this happens + // See https://github.com/elastic/beats/issues/6692 + defer func() { + if r := recover(); r != nil { + content = nil + err = fmt.Errorf("recovered panic when reading proc file '%s': %v", path, r) + } + }() contents, err := ioutil.ReadFile(path) if err != nil { diff --git a/vendor/github.com/elastic/gosigar/sigar_openbsd.go b/vendor/github.com/elastic/gosigar/sigar_openbsd.go index 4f1383a6ba..e4371b8b68 100644 --- a/vendor/github.com/elastic/gosigar/sigar_openbsd.go +++ b/vendor/github.com/elastic/gosigar/sigar_openbsd.go @@ -294,6 +294,10 @@ func (self *Swap) Get() error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (self *Cpu) Get() error { load := [C.CPUSTATES]C.long{C.CP_USER, C.CP_NICE, C.CP_SYS, C.CP_INTR, C.CP_IDLE} @@ -381,6 +385,10 @@ func (self *ProcFDUsage) Get(pid int) error { return ErrNotImplemented{runtime.GOOS} } +func (self *Rusage) Get(pid int) error { + return ErrNotImplemented{runtime.GOOS} +} + func fillCpu(cpu *Cpu, load [C.CPUSTATES]C.long) { cpu.User = uint64(load[0]) cpu.Nice = uint64(load[1]) diff --git a/vendor/github.com/elastic/gosigar/sigar_stub.go b/vendor/github.com/elastic/gosigar/sigar_stub.go index 0b858f1c0c..de9565aec4 100644 --- a/vendor/github.com/elastic/gosigar/sigar_stub.go +++ b/vendor/github.com/elastic/gosigar/sigar_stub.go @@ -22,6 +22,10 @@ func (s *Swap) Get() error { return ErrNotImplemented{runtime.GOOS} } +func (s *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (f *FDUsage) Get() error { return ErrNotImplemented{runtime.GOOS} } diff --git a/vendor/github.com/elastic/gosigar/sigar_windows.go b/vendor/github.com/elastic/gosigar/sigar_windows.go index 0cdf928d16..c2b54d8d7f 100644 --- a/vendor/github.com/elastic/gosigar/sigar_windows.go +++ b/vendor/github.com/elastic/gosigar/sigar_windows.go @@ -120,6 +120,10 @@ func (self *Swap) Get() error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (self *Cpu) Get() error { idle, kernel, user, err := windows.GetSystemTimes() if err != nil { diff --git a/vendor/github.com/fjl/memsize/LICENSE b/vendor/github.com/fjl/memsize/LICENSE new file mode 100644 index 0000000000..8b80456419 --- /dev/null +++ b/vendor/github.com/fjl/memsize/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Felix Lange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fjl/memsize/bitmap.go b/vendor/github.com/fjl/memsize/bitmap.go new file mode 100644 index 0000000000..47799ea8d3 --- /dev/null +++ b/vendor/github.com/fjl/memsize/bitmap.go @@ -0,0 +1,119 @@ +package memsize + +import ( + "math/bits" +) + +const ( + uintptrBits = 32 << (uint64(^uintptr(0)) >> 63) + uintptrBytes = uintptrBits / 8 + bmBlockRange = 1 * 1024 * 1024 // bytes covered by bmBlock + bmBlockWords = bmBlockRange / uintptrBits +) + +// bitmap is a sparse bitmap. +type bitmap struct { + blocks map[uintptr]*bmBlock +} + +func newBitmap() *bitmap { + return &bitmap{make(map[uintptr]*bmBlock)} +} + +// markRange sets n consecutive bits starting at addr. +func (b *bitmap) markRange(addr, n uintptr) { + for end := addr + n; addr < end; { + block, baddr := b.block(addr) + for i := baddr; i < bmBlockRange && addr < end; i++ { + block.mark(i) + addr++ + } + } +} + +// isMarked returns the value of the bit at the given address. +func (b *bitmap) isMarked(addr uintptr) bool { + block, baddr := b.block(addr) + return block.isMarked(baddr) +} + +// countRange returns the number of set bits in the range (addr,addr+n). +func (b *bitmap) countRange(addr, n uintptr) uintptr { + c := uintptr(0) + for end := addr + n; addr < end; { + block, baddr := b.block(addr) + bend := uintptr(bmBlockRange - 1) + if baddr+(end-addr) < bmBlockRange { + bend = baddr + (end - addr) + } + c += uintptr(block.count(baddr, bend)) + // Move addr to next block. + addr += bmBlockRange - baddr + } + return c +} + +// block finds the block corresponding to the given memory address. +// It also returns the block's starting address. +func (b *bitmap) block(addr uintptr) (*bmBlock, uintptr) { + index := addr / bmBlockRange + block := b.blocks[index] + if block == nil { + block = new(bmBlock) + b.blocks[index] = block + } + return block, addr % bmBlockRange +} + +// size returns the sum of the byte sizes of all blocks. +func (b *bitmap) size() uintptr { + return uintptr(len(b.blocks)) * bmBlockWords * uintptrBytes +} + +// utilization returns the mean percentage of one bits across all blocks. +func (b *bitmap) utilization() float32 { + var avg float32 + for _, block := range b.blocks { + avg += float32(block.count(0, bmBlockRange-1)) / float32(bmBlockRange) + } + return avg / float32(len(b.blocks)) +} + +// bmBlock is a bitmap block. +type bmBlock [bmBlockWords]uintptr + +// mark sets the i'th bit to one. +func (b *bmBlock) mark(i uintptr) { + b[i/uintptrBits] |= 1 << (i % uintptrBits) +} + +// isMarked returns the value of the i'th bit. +func (b *bmBlock) isMarked(i uintptr) bool { + return (b[i/uintptrBits] & (1 << (i % uintptrBits))) != 0 +} + +// count returns the number of set bits in the range (start,end). +func (b *bmBlock) count(start, end uintptr) (count int) { + br := b[start/uintptrBits : end/uintptrBits+1] + for i, w := range br { + if i == 0 { + w &= blockmask(start) + } + if i == len(br)-1 { + w &^= blockmask(end) + } + count += onesCountPtr(w) + } + return count +} + +func blockmask(x uintptr) uintptr { + return ^uintptr(0) << (x % uintptrBits) +} + +func onesCountPtr(x uintptr) int { + if uintptrBits == 64 { + return bits.OnesCount64(uint64(x)) + } + return bits.OnesCount32(uint32(x)) +} diff --git a/vendor/github.com/fjl/memsize/doc.go b/vendor/github.com/fjl/memsize/doc.go new file mode 100644 index 0000000000..640cfba5eb --- /dev/null +++ b/vendor/github.com/fjl/memsize/doc.go @@ -0,0 +1,16 @@ +/* +Package memsize computes the size of your object graph. + +So you made a spiffy algorithm and it works really well, but geez it's using +way too much memory. Where did it all go? memsize to the rescue! + +To get started, find a value that references all your objects and scan it. +This traverses the graph, counting sizes per type. + + sizes := memsize.Scan(myValue) + fmt.Println(sizes.Total) + +memsize can handle cycles just fine and tracks both private and public struct fields. +Unfortunately function closures cannot be inspected in any way. +*/ +package memsize diff --git a/vendor/github.com/fjl/memsize/memsize.go b/vendor/github.com/fjl/memsize/memsize.go new file mode 100644 index 0000000000..2664e87c46 --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsize.go @@ -0,0 +1,243 @@ +package memsize + +import ( + "bytes" + "fmt" + "reflect" + "sort" + "strings" + "text/tabwriter" + "unsafe" +) + +// Scan traverses all objects reachable from v and counts how much memory +// is used per type. The value must be a non-nil pointer to any value. +func Scan(v interface{}) Sizes { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + panic("value to scan must be non-nil pointer") + } + + stopTheWorld("memsize scan") + defer startTheWorld() + + ctx := newContext() + ctx.scan(invalidAddr, rv, false) + ctx.s.BitmapSize = ctx.seen.size() + ctx.s.BitmapUtilization = ctx.seen.utilization() + return *ctx.s +} + +// Sizes is the result of a scan. +type Sizes struct { + Total uintptr + ByType map[reflect.Type]*TypeSize + // Internal stats (for debugging) + BitmapSize uintptr + BitmapUtilization float32 +} + +type TypeSize struct { + Total uintptr + Count uintptr +} + +func newSizes() *Sizes { + return &Sizes{ByType: make(map[reflect.Type]*TypeSize)} +} + +// Report returns a human-readable report. +func (s Sizes) Report() string { + type typLine struct { + name string + count uintptr + total uintptr + } + tab := []typLine{{"ALL", 0, s.Total}} + for _, typ := range s.ByType { + tab[0].count += typ.Count + } + maxname := 0 + for typ, s := range s.ByType { + line := typLine{typ.String(), s.Count, s.Total} + tab = append(tab, line) + if len(line.name) > maxname { + maxname = len(line.name) + } + } + sort.Slice(tab, func(i, j int) bool { return tab[i].total > tab[j].total }) + + buf := new(bytes.Buffer) + w := tabwriter.NewWriter(buf, 0, 0, 0, ' ', tabwriter.AlignRight) + for _, line := range tab { + namespace := strings.Repeat(" ", maxname-len(line.name)) + fmt.Fprintf(w, "%s%s\t %v\t %s\t\n", line.name, namespace, line.count, HumanSize(line.total)) + } + w.Flush() + return buf.String() +} + +// addValue is called during scan and adds the memory of given object. +func (s *Sizes) addValue(v reflect.Value, size uintptr) { + s.Total += size + rs := s.ByType[v.Type()] + if rs == nil { + rs = new(TypeSize) + s.ByType[v.Type()] = rs + } + rs.Total += size + rs.Count++ +} + +type context struct { + // We track previously scanned objects to prevent infinite loops + // when scanning cycles and to prevent counting objects more than once. + seen *bitmap + tc typCache + s *Sizes +} + +func newContext() *context { + return &context{seen: newBitmap(), tc: make(typCache), s: newSizes()} +} + +// scan walks all objects below v, determining their size. All scan* functions return the +// amount of 'extra' memory (e.g. slice data) that is referenced by the object. +func (c *context) scan(addr address, v reflect.Value, add bool) (extraSize uintptr) { + size := v.Type().Size() + var marked uintptr + if addr.valid() { + marked = c.seen.countRange(uintptr(addr), size) + if marked == size { + return 0 // Skip if we have already seen the whole object. + } + c.seen.markRange(uintptr(addr), size) + } + // fmt.Printf("%v: %v ⮑ (marked %d)\n", addr, v.Type(), marked) + if c.tc.needScan(v.Type()) { + extraSize = c.scanContent(addr, v) + } + // fmt.Printf("%v: %v %d (add %v, size %d, marked %d, extra %d)\n", addr, v.Type(), size+extraSize, add, v.Type().Size(), marked, extraSize) + if add { + size -= marked + size += extraSize + c.s.addValue(v, size) + } + return extraSize +} + +func (c *context) scanContent(addr address, v reflect.Value) uintptr { + switch v.Kind() { + case reflect.Array: + return c.scanArray(addr, v) + case reflect.Chan: + return c.scanChan(v) + case reflect.Func: + // can't do anything here + return 0 + case reflect.Interface: + return c.scanInterface(v) + case reflect.Map: + return c.scanMap(v) + case reflect.Ptr: + if !v.IsNil() { + c.scan(address(v.Pointer()), v.Elem(), true) + } + return 0 + case reflect.Slice: + return c.scanSlice(v) + case reflect.String: + return uintptr(v.Len()) + case reflect.Struct: + return c.scanStruct(addr, v) + default: + unhandledKind(v.Kind()) + return 0 + } +} + +func (c *context) scanChan(v reflect.Value) uintptr { + etyp := v.Type().Elem() + extra := uintptr(0) + if c.tc.needScan(etyp) { + // Scan the channel buffer. This is unsafe but doesn't race because + // the world is stopped during scan. + hchan := unsafe.Pointer(v.Pointer()) + for i := uint(0); i < uint(v.Cap()); i++ { + addr := chanbuf(hchan, i) + elem := reflect.NewAt(etyp, addr).Elem() + extra += c.scanContent(address(addr), elem) + } + } + return uintptr(v.Cap())*etyp.Size() + extra +} + +func (c *context) scanStruct(base address, v reflect.Value) uintptr { + extra := uintptr(0) + for i := 0; i < v.NumField(); i++ { + f := v.Type().Field(i) + if c.tc.needScan(f.Type) { + addr := base.addOffset(f.Offset) + extra += c.scanContent(addr, v.Field(i)) + } + } + return extra +} + +func (c *context) scanArray(addr address, v reflect.Value) uintptr { + esize := v.Type().Elem().Size() + extra := uintptr(0) + for i := 0; i < v.Len(); i++ { + extra += c.scanContent(addr, v.Index(i)) + addr = addr.addOffset(esize) + } + return extra +} + +func (c *context) scanSlice(v reflect.Value) uintptr { + slice := v.Slice(0, v.Cap()) + esize := slice.Type().Elem().Size() + base := slice.Pointer() + // Add size of the unscanned portion of the backing array to extra. + blen := uintptr(slice.Len()) * esize + marked := c.seen.countRange(base, blen) + extra := blen - marked + c.seen.markRange(uintptr(base), blen) + if c.tc.needScan(slice.Type().Elem()) { + // Elements may contain pointers, scan them individually. + addr := address(base) + for i := 0; i < slice.Len(); i++ { + extra += c.scanContent(addr, slice.Index(i)) + addr = addr.addOffset(esize) + } + } + return extra +} + +func (c *context) scanMap(v reflect.Value) uintptr { + var ( + typ = v.Type() + len = uintptr(v.Len()) + extra = uintptr(0) + ) + if c.tc.needScan(typ.Key()) || c.tc.needScan(typ.Elem()) { + for _, k := range v.MapKeys() { + extra += c.scan(invalidAddr, k, false) + extra += c.scan(invalidAddr, v.MapIndex(k), false) + } + } + return len*typ.Key().Size() + len*typ.Elem().Size() + extra +} + +func (c *context) scanInterface(v reflect.Value) uintptr { + elem := v.Elem() + if !elem.IsValid() { + return 0 // nil interface + } + c.scan(invalidAddr, elem, false) + if !c.tc.isPointer(elem.Type()) { + // Account for non-pointer size of the value. + return elem.Type().Size() + } + return 0 +} diff --git a/vendor/github.com/fjl/memsize/memsizeui/template.go b/vendor/github.com/fjl/memsize/memsizeui/template.go new file mode 100644 index 0000000000..b60fe6ba54 --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsizeui/template.go @@ -0,0 +1,106 @@ +package memsizeui + +import ( + "html/template" + "strconv" + "sync" + + "github.com/fjl/memsize" +) + +var ( + base *template.Template // the "base" template + baseInitOnce sync.Once +) + +func baseInit() { + base = template.Must(template.New("base").Parse(` + + + + memsize + + + + {{template "content" .}} + +`)) + + base.Funcs(template.FuncMap{ + "quote": strconv.Quote, + "humansize": memsize.HumanSize, + }) + + template.Must(base.New("rootbuttons").Parse(` +Overview +{{- range $root := .Roots -}} +
+ +
+{{- end -}}`)) +} + +func contentTemplate(source string) *template.Template { + baseInitOnce.Do(baseInit) + t := template.Must(base.Clone()) + template.Must(t.New("content").Parse(source)) + return t +} + +var rootTemplate = contentTemplate(` +

Memsize

+{{template "rootbuttons" .}} +
+

Reports

+ +`) + +var notFoundTemplate = contentTemplate(` +

{{.Data}}

+{{template "rootbuttons" .}} +`) + +var reportTemplate = contentTemplate(` +{{- $report := .Data -}} +

Memsize Report {{$report.ID}}

+
+ Overview + +
+
+Root: {{quote $report.RootName}}
+Date: {{$report.Date}}
+Duration: {{$report.Duration}}
+Bitmap Size: {{$report.Sizes.BitmapSize | humansize}}
+Bitmap Utilization: {{$report.Sizes.BitmapUtilization}}
+
+
+
+{{$report.Sizes.Report}}
+
+`) diff --git a/vendor/github.com/fjl/memsize/memsizeui/ui.go b/vendor/github.com/fjl/memsize/memsizeui/ui.go new file mode 100644 index 0000000000..c48fc53f7f --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsizeui/ui.go @@ -0,0 +1,153 @@ +package memsizeui + +import ( + "bytes" + "fmt" + "html/template" + "net/http" + "reflect" + "sort" + "strings" + "sync" + "time" + + "github.com/fjl/memsize" +) + +type Handler struct { + init sync.Once + mux http.ServeMux + mu sync.Mutex + reports map[int]Report + roots map[string]interface{} + reportID int +} + +type Report struct { + ID int + Date time.Time + Duration time.Duration + RootName string + Sizes memsize.Sizes +} + +type templateInfo struct { + Roots []string + Reports map[int]Report + PathDepth int + Data interface{} +} + +func (ti *templateInfo) Link(path ...string) string { + prefix := strings.Repeat("../", ti.PathDepth) + return prefix + strings.Join(path, "") +} + +func (h *Handler) Add(name string, v interface{}) { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + panic("root must be non-nil pointer") + } + h.mu.Lock() + if h.roots == nil { + h.roots = make(map[string]interface{}) + } + h.roots[name] = v + h.mu.Unlock() +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.init.Do(func() { + h.reports = make(map[int]Report) + h.mux.HandleFunc("/", h.handleRoot) + h.mux.HandleFunc("/scan", h.handleScan) + h.mux.HandleFunc("/report/", h.handleReport) + }) + h.mux.ServeHTTP(w, r) +} + +func (h *Handler) templateInfo(r *http.Request, data interface{}) *templateInfo { + h.mu.Lock() + roots := make([]string, 0, len(h.roots)) + for name := range h.roots { + roots = append(roots, name) + } + h.mu.Unlock() + sort.Strings(roots) + + return &templateInfo{ + Roots: roots, + Reports: h.reports, + PathDepth: strings.Count(r.URL.Path, "/") - 1, + Data: data, + } +} + +func (h *Handler) handleRoot(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + serveHTML(w, rootTemplate, http.StatusOK, h.templateInfo(r, nil)) +} + +func (h *Handler) handleScan(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "invalid HTTP method, want POST", http.StatusMethodNotAllowed) + return + } + ti := h.templateInfo(r, "Unknown root") + id, ok := h.scan(r.URL.Query().Get("root")) + if !ok { + serveHTML(w, notFoundTemplate, http.StatusNotFound, ti) + return + } + w.Header().Add("Location", ti.Link(fmt.Sprintf("report/%d", id))) + w.WriteHeader(http.StatusSeeOther) +} + +func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) { + var id int + fmt.Sscan(strings.TrimPrefix(r.URL.Path, "/report/"), &id) + h.mu.Lock() + report, ok := h.reports[id] + h.mu.Unlock() + + if !ok { + serveHTML(w, notFoundTemplate, http.StatusNotFound, h.templateInfo(r, "Report not found")) + } else { + serveHTML(w, reportTemplate, http.StatusOK, h.templateInfo(r, report)) + } +} + +func (h *Handler) scan(root string) (int, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + val, ok := h.roots[root] + if !ok { + return 0, false + } + id := h.reportID + start := time.Now() + sizes := memsize.Scan(val) + h.reports[id] = Report{ + ID: id, + RootName: root, + Date: start.Truncate(1 * time.Second), + Duration: time.Since(start), + Sizes: sizes, + } + h.reportID++ + return id, true +} + +func serveHTML(w http.ResponseWriter, tpl *template.Template, status int, ti *templateInfo) { + w.Header().Set("content-type", "text/html") + var buf bytes.Buffer + if err := tpl.Execute(&buf, ti); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + buf.WriteTo(w) +} diff --git a/vendor/github.com/fjl/memsize/runtimefunc.go b/vendor/github.com/fjl/memsize/runtimefunc.go new file mode 100644 index 0000000000..912a3e768d --- /dev/null +++ b/vendor/github.com/fjl/memsize/runtimefunc.go @@ -0,0 +1,14 @@ +package memsize + +import "unsafe" + +var _ = unsafe.Pointer(nil) + +//go:linkname stopTheWorld runtime.stopTheWorld +func stopTheWorld(reason string) + +//go:linkname startTheWorld runtime.startTheWorld +func startTheWorld() + +//go:linkname chanbuf runtime.chanbuf +func chanbuf(ch unsafe.Pointer, i uint) unsafe.Pointer diff --git a/vendor/github.com/fjl/memsize/runtimefunc.s b/vendor/github.com/fjl/memsize/runtimefunc.s new file mode 100644 index 0000000000..a091e2fa72 --- /dev/null +++ b/vendor/github.com/fjl/memsize/runtimefunc.s @@ -0,0 +1 @@ +// This file is required to make stub function declarations work. diff --git a/vendor/github.com/fjl/memsize/type.go b/vendor/github.com/fjl/memsize/type.go new file mode 100644 index 0000000000..5d6f59e9ff --- /dev/null +++ b/vendor/github.com/fjl/memsize/type.go @@ -0,0 +1,119 @@ +package memsize + +import ( + "fmt" + "reflect" +) + +// address is a memory location. +// +// Code dealing with uintptr is oblivious to the zero address. +// Code dealing with address is not: it treats the zero address +// as invalid. Offsetting an invalid address doesn't do anything. +// +// This distinction is useful because there are objects that we can't +// get the pointer to. +type address uintptr + +const invalidAddr = address(0) + +func (a address) valid() bool { + return a != 0 +} + +func (a address) addOffset(off uintptr) address { + if !a.valid() { + return invalidAddr + } + return a + address(off) +} + +func (a address) String() string { + if uintptrBits == 32 { + return fmt.Sprintf("%#0.8x", uintptr(a)) + } + return fmt.Sprintf("%#0.16x", uintptr(a)) +} + +type typCache map[reflect.Type]typInfo + +type typInfo struct { + isPointer bool + needScan bool +} + +// isPointer returns true for pointer-ish values. The notion of +// pointer includes everything but plain values, i.e. slices, maps +// channels, interfaces are 'pointer', too. +func (tc *typCache) isPointer(typ reflect.Type) bool { + return tc.info(typ).isPointer +} + +// needScan reports whether a value of the type needs to be scanned +// recursively because it may contain pointers. +func (tc *typCache) needScan(typ reflect.Type) bool { + return tc.info(typ).needScan +} + +func (tc *typCache) info(typ reflect.Type) typInfo { + info, found := (*tc)[typ] + switch { + case found: + return info + case isPointer(typ): + info = typInfo{true, true} + default: + info = typInfo{false, tc.checkNeedScan(typ)} + } + (*tc)[typ] = info + return info +} + +func (tc *typCache) checkNeedScan(typ reflect.Type) bool { + switch k := typ.Kind(); k { + case reflect.Struct: + // Structs don't need scan if none of their fields need it. + for i := 0; i < typ.NumField(); i++ { + if tc.needScan(typ.Field(i).Type) { + return true + } + } + case reflect.Array: + // Arrays don't need scan if their element type doesn't. + return tc.needScan(typ.Elem()) + } + return false +} + +func isPointer(typ reflect.Type) bool { + k := typ.Kind() + switch { + case k <= reflect.Complex128: + return false + case k == reflect.Array: + return false + case k >= reflect.Chan && k <= reflect.String: + return true + case k == reflect.Struct || k == reflect.UnsafePointer: + return false + default: + unhandledKind(k) + return false + } +} + +func unhandledKind(k reflect.Kind) { + panic("unhandled kind " + k.String()) +} + +// HumanSize formats the given number of bytes as a readable string. +func HumanSize(bytes uintptr) string { + switch { + case bytes < 1024: + return fmt.Sprintf("%d B", bytes) + case bytes < 1024*1024: + return fmt.Sprintf("%.3f KB", float64(bytes)/1024) + default: + return fmt.Sprintf("%.3f MB", float64(bytes)/1024/1024) + } +} diff --git a/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go b/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go index 9b0421f035..838f1bee1b 100644 --- a/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go +++ b/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go @@ -12,7 +12,11 @@ import ( "sync" ) -const typeShift = 3 +const typeShift = 4 + +// Verify at compile-time that typeShift is large enough to cover all FileType +// values by confirming that 0 == 0. +var _ [0]struct{} = [TypeAll >> typeShift]struct{}{} type memStorageLock struct { ms *memStorage @@ -143,7 +147,7 @@ func (ms *memStorage) Remove(fd FileDesc) error { } func (ms *memStorage) Rename(oldfd, newfd FileDesc) error { - if FileDescOk(oldfd) || FileDescOk(newfd) { + if !FileDescOk(oldfd) || !FileDescOk(newfd) { return ErrInvalidFile } if oldfd == newfd { diff --git a/vendor/github.com/syndtr/goleveldb/leveldb/util.go b/vendor/github.com/syndtr/goleveldb/leveldb/util.go index e572a329e9..0e2b519e5c 100644 --- a/vendor/github.com/syndtr/goleveldb/leveldb/util.go +++ b/vendor/github.com/syndtr/goleveldb/leveldb/util.go @@ -20,7 +20,7 @@ func shorten(str string) string { return str[:3] + ".." + str[len(str)-3:] } -var bunits = [...]string{"", "Ki", "Mi", "Gi"} +var bunits = [...]string{"", "Ki", "Mi", "Gi", "Ti"} func shortenb(bytes int) string { i := 0 diff --git a/vendor/vendor.json b/vendor/vendor.json index bf55bc437f..fdc7789364 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -93,10 +93,10 @@ "revisionTime": "2016-05-12T03:30:02Z" }, { - "checksumSHA1": "Fc8BCxCoQ7ZmghDT6X1cASR10Ec=", + "checksumSHA1": "jElNoLEe7m/iaoF1vYIHyNaS2SE=", "path": "github.com/elastic/gosigar", - "revision": "a3814ce5008e612a0c6d027608b54e1d0d9a5613", - "revisionTime": "2018-01-22T22:25:45Z" + "revision": "37f05ff46ffa7a825d1b24cf2b62d4a4c1a9d2e8", + "revisionTime": "2018-03-30T10:04:40Z" }, { "checksumSHA1": "qDsgp2kAeI9nhj565HUScaUyjU4=", @@ -110,6 +110,18 @@ "revision": "5ec5d9d3c2cf82e9688b34e9bc27a94d616a7193", "revisionTime": "2017-02-09T08:00:14Z" }, + { + "checksumSHA1": "Jq1rrHSGPfh689nA2hL1QVb62zE=", + "path": "github.com/fjl/memsize", + "revision": "ca190fb6ffbc076ff49197b7168a760f30182d2e", + "revisionTime": "2018-04-18T12:24:29Z" + }, + { + "checksumSHA1": "Z13QAYTqeW4cTiglkc2F05gWLu4=", + "path": "github.com/fjl/memsize/memsizeui", + "revision": "ca190fb6ffbc076ff49197b7168a760f30182d2e", + "revisionTime": "2018-04-18T12:24:29Z" + }, { "checksumSHA1": "0orwvPL96wFckVJyPl39fz2QsgA=", "path": "github.com/gizak/termui", @@ -406,76 +418,76 @@ "revisionTime": "2017-07-05T02:17:15Z" }, { - "checksumSHA1": "3QsnhPTXGytTbW3uDvQLgSo9s9M=", + "checksumSHA1": "k13cCuMJO7+KhR8ZXx5oUqDKGQA=", "path": "github.com/syndtr/goleveldb/leveldb", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "EKIow7XkgNdWvR/982ffIZxKG8Y=", "path": "github.com/syndtr/goleveldb/leveldb/cache", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "5KPgnvCPlR0ysDAqo6jApzRQ3tw=", "path": "github.com/syndtr/goleveldb/leveldb/comparer", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "1DRAxdlWzS4U0xKN/yQ/fdNN7f0=", "path": "github.com/syndtr/goleveldb/leveldb/errors", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "eqKeD6DS7eNCtxVYZEHHRKkyZrw=", "path": "github.com/syndtr/goleveldb/leveldb/filter", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "weSsccMav4BCerDpSLzh3mMxAYo=", "path": "github.com/syndtr/goleveldb/leveldb/iterator", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "gJY7bRpELtO0PJpZXgPQ2BYFJ88=", "path": "github.com/syndtr/goleveldb/leveldb/journal", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "MtYY1b2234y/MlS+djL8tXVAcQs=", "path": "github.com/syndtr/goleveldb/leveldb/memdb", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "UmQeotV+m8/FduKEfLOhjdp18rs=", "path": "github.com/syndtr/goleveldb/leveldb/opt", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { - "checksumSHA1": "QCSae2ub87f8awH+PKMpd8ZYOtg=", + "checksumSHA1": "7H3fa12T7WoMAeXq1+qG5O7LD0w=", "path": "github.com/syndtr/goleveldb/leveldb/storage", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "gWFPMz8OQeul0t54RM66yMTX49g=", "path": "github.com/syndtr/goleveldb/leveldb/table", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "V/Dh7NV0/fy/5jX1KaAjmGcNbzI=", "path": "github.com/syndtr/goleveldb/leveldb/util", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=", diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go index 57e6505ad1..d32eaddec3 100644 --- a/whisper/mailserver/mailserver.go +++ b/whisper/mailserver/mailserver.go @@ -20,7 +20,6 @@ import ( "encoding/binary" "fmt" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -54,19 +53,19 @@ func NewDbKey(t uint32, h common.Hash) *DBKey { return &k } -func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) { +func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, pow float64) error { var err error if len(path) == 0 { - utils.Fatalf("DB file is not specified") + return fmt.Errorf("DB file is not specified") } if len(password) == 0 { - utils.Fatalf("Password is not specified for MailServer") + return fmt.Errorf("password is not specified") } s.db, err = leveldb.OpenFile(path, nil) if err != nil { - utils.Fatalf("Failed to open DB file: %s", err) + return fmt.Errorf("open DB file: %s", err) } s.w = shh @@ -74,12 +73,13 @@ func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, p MailServerKeyID, err := s.w.AddSymKeyFromPassword(password) if err != nil { - utils.Fatalf("Failed to create symmetric key for MailServer: %s", err) + return fmt.Errorf("create symmetric key: %s", err) } s.key, err = s.w.GetSymKey(MailServerKeyID) if err != nil { - utils.Fatalf("Failed to save symmetric key for MailServer") + return fmt.Errorf("save symmetric key: %s", err) } + return nil } func (s *WMailServer) Close() { diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index d5b993afb9..edb817cc75 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -92,7 +92,10 @@ func TestMailServer(t *testing.T) { shh = whisper.New(&whisper.DefaultConfig) shh.RegisterServer(&server) - server.Init(shh, dir, password, powRequirement) + err = server.Init(shh, dir, password, powRequirement) + if err != nil { + t.Fatal(err) + } defer server.Close() keyID, err = shh.AddSymKeyFromPassword(password) diff --git a/whisper/shhclient/client.go b/whisper/shhclient/client.go index bbe694baaa..8e7085a0a6 100644 --- a/whisper/shhclient/client.go +++ b/whisper/shhclient/client.go @@ -67,7 +67,6 @@ func (sc *Client) SetMaxMessageSize(ctx context.Context, size uint32) error { } // SetMinimumPoW (experimental) sets the minimal PoW required by this node. - // This experimental function was introduced for the future dynamic adjustment of // PoW requirement. If the node is overwhelmed with messages, it should raise the // PoW requirement and notify the peers. The new value should be set relative to @@ -77,7 +76,7 @@ func (sc *Client) SetMinimumPoW(ctx context.Context, pow float64) error { return sc.c.CallContext(ctx, &ignored, "shh_setMinPoW", pow) } -// Marks specific peer trusted, which will allow it to send historic (expired) messages. +// MarkTrustedPeer marks specific peer trusted, which will allow it to send historic (expired) messages. // Note This function is not adding new nodes, the node needs to exists as a peer. func (sc *Client) MarkTrustedPeer(ctx context.Context, enode string) error { var ignored bool @@ -136,9 +135,9 @@ func (sc *Client) AddSymmetricKey(ctx context.Context, key []byte) (string, erro } // GenerateSymmetricKeyFromPassword generates the key from password, stores it, and returns its identifier. -func (sc *Client) GenerateSymmetricKeyFromPassword(ctx context.Context, passwd []byte) (string, error) { +func (sc *Client) GenerateSymmetricKeyFromPassword(ctx context.Context, passwd string) (string, error) { var id string - return id, sc.c.CallContext(ctx, &id, "shh_generateSymKeyFromPassword", hexutil.Bytes(passwd)) + return id, sc.c.CallContext(ctx, &id, "shh_generateSymKeyFromPassword", passwd) } // HasSymmetricKey returns an indication if the key associated with the given id is stored in the node. diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index ee566625c9..c56d139499 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -32,10 +32,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -const ( - filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds -) - var ( ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") ErrInvalidSymmetricKey = errors.New("invalid symmetric key") @@ -93,7 +89,7 @@ func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) return true, api.w.SetMaxMessageSize(size) } -// SetMinPow sets the minimum PoW for a message before it is accepted. +// SetMinPoW sets the minimum PoW for a message before it is accepted. func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { return true, api.w.SetMinimumPoW(pow) } @@ -146,7 +142,7 @@ func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexut return crypto.FromECDSAPub(&key.PublicKey), nil } -// GetPublicKey returns the private key associated with the given key. The key is the hex +// GetPrivateKey returns the private key associated with the given key. The key is the hex // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) { key, err := api.w.GetPrivateKey(id) diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 7a57488bd7..8161db8ed6 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -15,7 +15,7 @@ // along with the go-ethereum library. If not, see . /* -Package whisper implements the Whisper protocol (version 5). +Package whisperv5 implements the Whisper protocol (version 5). Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP). As such it may be likened and compared to both, not dissimilar to the diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index 34ce52e64f..35711d724b 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// Options specifies the exact way a message should be wrapped into an Envelope. +// MessageParams specifies the exact way a message should be wrapped into an Envelope. type MessageParams struct { TTL uint32 Src *ecdsa.PrivateKey @@ -86,7 +86,7 @@ func (msg *ReceivedMessage) isAsymmetricEncryption() bool { return msg.Dst != nil } -// NewMessage creates and initializes a non-signed, non-encrypted Whisper message. +// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message. func NewSentMessage(params *MessageParams) (*sentMessage, error) { msg := sentMessage{} msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit) @@ -330,7 +330,7 @@ func (msg *ReceivedMessage) extractPadding(end int) (int, bool) { return paddingSize, true } -// Recover retrieves the public key of the message signer. +// SigToPubKey retrieves the public key of the message signer. func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { defer func() { recover() }() // in case of invalid signature diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go index 179c931795..da07631992 100644 --- a/whisper/whisperv5/peer.go +++ b/whisper/whisperv5/peer.go @@ -27,7 +27,7 @@ import ( set "gopkg.in/fatih/set.v0" ) -// peer represents a whisper protocol peer connection. +// Peer represents a whisper protocol peer connection. type Peer struct { host *Whisper peer *p2p.Peer @@ -53,51 +53,51 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer { // start initiates the peer updater, periodically broadcasting the whisper packets // into the network. -func (p *Peer) start() { - go p.update() - log.Trace("start", "peer", p.ID()) +func (peer *Peer) start() { + go peer.update() + log.Trace("start", "peer", peer.ID()) } // stop terminates the peer updater, stopping message forwarding to it. -func (p *Peer) stop() { - close(p.quit) - log.Trace("stop", "peer", p.ID()) +func (peer *Peer) stop() { + close(peer.quit) + log.Trace("stop", "peer", peer.ID()) } // handshake sends the protocol initiation status message to the remote peer and // verifies the remote status too. -func (p *Peer) handshake() error { +func (peer *Peer) handshake() error { // Send the handshake status message asynchronously errc := make(chan error, 1) go func() { - errc <- p2p.Send(p.ws, statusCode, ProtocolVersion) + errc <- p2p.Send(peer.ws, statusCode, ProtocolVersion) }() // Fetch the remote status packet and verify protocol match - packet, err := p.ws.ReadMsg() + packet, err := peer.ws.ReadMsg() if err != nil { return err } if packet.Code != statusCode { - return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code) + return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code) } s := rlp.NewStream(packet.Payload, uint64(packet.Size)) peerVersion, err := s.Uint() if err != nil { - return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err) + return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err) } if peerVersion != ProtocolVersion { - return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion) + return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion) } // Wait until out own status is consumed too if err := <-errc; err != nil { - return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err) + return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err) } return nil } // update executes periodic operations on the peer, including message transmission // and expiration. -func (p *Peer) update() { +func (peer *Peer) update() { // Start the tickers for the updates expire := time.NewTicker(expirationCycle) transmit := time.NewTicker(transmissionCycle) @@ -106,15 +106,15 @@ func (p *Peer) update() { for { select { case <-expire.C: - p.expire() + peer.expire() case <-transmit.C: - if err := p.broadcast(); err != nil { - log.Trace("broadcast failed", "reason", err, "peer", p.ID()) + if err := peer.broadcast(); err != nil { + log.Trace("broadcast failed", "reason", err, "peer", peer.ID()) return } - case <-p.quit: + case <-peer.quit: return } } @@ -148,16 +148,16 @@ func (peer *Peer) expire() { // broadcast iterates over the collection of envelopes and transmits yet unknown // ones over the network. -func (p *Peer) broadcast() error { +func (peer *Peer) broadcast() error { var cnt int - envelopes := p.host.Envelopes() + envelopes := peer.host.Envelopes() for _, envelope := range envelopes { - if !p.marked(envelope) { - err := p2p.Send(p.ws, messagesCode, envelope) + if !peer.marked(envelope) { + err := p2p.Send(peer.ws, messagesCode, envelope) if err != nil { return err } else { - p.mark(envelope) + peer.mark(envelope) cnt++ } } @@ -168,7 +168,7 @@ func (p *Peer) broadcast() error { return nil } -func (p *Peer) ID() []byte { - id := p.peer.ID() +func (peer *Peer) ID() []byte { + id := peer.peer.ID() return id[:] } diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index bae2adb6f5..051b52dcf8 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" ) -var keys []string = []string{ +var keys = []string{ "d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9", "73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98", "119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc", @@ -84,9 +84,9 @@ type TestNode struct { var result TestData var nodes [NumNodes]*TestNode -var sharedKey []byte = []byte("some arbitrary data here") +var sharedKey = []byte("some arbitrary data here") var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0} -var expectedMessage []byte = []byte("per rectum ad astra") +var expectedMessage = []byte("per rectum ad astra") // This test does the following: // 1. creates a chain of whisper nodes, diff --git a/whisper/whisperv5/topic.go b/whisper/whisperv5/topic.go index c4ea67eefa..c4eda1db42 100644 --- a/whisper/whisperv5/topic.go +++ b/whisper/whisperv5/topic.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -// Topic represents a cryptographically secure, probabilistic partial +// TopicType represents a cryptographically secure, probabilistic partial // classifications of a message, determined as the first (left) 4 bytes of the // SHA3 hash of some arbitrary data given by the original author of the message. type TopicType [TopicLength]byte diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 85849ccce4..62bd1ce179 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -469,18 +469,18 @@ func (w *Whisper) Stop() error { // HandlePeer is called by the underlying P2P layer when the whisper sub-protocol // connection is negotiated. -func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { +func (w *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { // Create the new peer and start tracking it - whisperPeer := newPeer(wh, peer, rw) + whisperPeer := newPeer(w, peer, rw) - wh.peerMu.Lock() - wh.peers[whisperPeer] = struct{}{} - wh.peerMu.Unlock() + w.peerMu.Lock() + w.peers[whisperPeer] = struct{}{} + w.peerMu.Unlock() defer func() { - wh.peerMu.Lock() - delete(wh.peers, whisperPeer) - wh.peerMu.Unlock() + w.peerMu.Lock() + delete(w.peers, whisperPeer) + w.peerMu.Unlock() }() // Run the peer handshake and state updates @@ -490,11 +490,11 @@ func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { whisperPeer.start() defer whisperPeer.stop() - return wh.runMessageLoop(whisperPeer, rw) + return w.runMessageLoop(whisperPeer, rw) } // runMessageLoop reads and processes inbound messages directly to merge into client-global state. -func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { +func (w *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { for { // fetch the next packet packet, err := rw.ReadMsg() @@ -502,7 +502,7 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("message loop", "peer", p.peer.ID(), "err", err) return err } - if packet.Size > wh.MaxMessageSize() { + if packet.Size > w.MaxMessageSize() { log.Warn("oversized message received", "peer", p.peer.ID()) return errors.New("oversized message received") } @@ -518,7 +518,7 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid envelope") } - cached, err := wh.add(&envelope) + cached, err := w.add(&envelope) if err != nil { log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid envelope") @@ -537,17 +537,17 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid direct message") } - wh.postEvent(&envelope, true) + w.postEvent(&envelope, true) } case p2pRequestCode: // Must be processed if mail server is implemented. Otherwise ignore. - if wh.mailServer != nil { + if w.mailServer != nil { var request Envelope if err := packet.Decode(&request); err != nil { log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid p2p request") } - wh.mailServer.DeliverMail(p, &request) + w.mailServer.DeliverMail(p, &request) } default: // New message types might be implemented in the future versions of Whisper. @@ -561,29 +561,27 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { // add inserts a new envelope into the message pool to be distributed within the // whisper network. It also inserts the envelope into the expiration pool at the // appropriate time-stamp. In case of error, connection should be dropped. -func (wh *Whisper) add(envelope *Envelope) (bool, error) { +func (w *Whisper) add(envelope *Envelope) (bool, error) { now := uint32(time.Now().Unix()) sent := envelope.Expiry - envelope.TTL if sent > now { if sent-SynchAllowance > now { return false, fmt.Errorf("envelope created in the future [%x]", envelope.Hash()) - } else { - // recalculate PoW, adjusted for the time difference, plus one second for latency - envelope.calculatePoW(sent - now + 1) } + // recalculate PoW, adjusted for the time difference, plus one second for latency + envelope.calculatePoW(sent - now + 1) } if envelope.Expiry < now { if envelope.Expiry+SynchAllowance*2 < now { return false, fmt.Errorf("very old message") - } else { - log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) - return false, nil // drop envelope without error } + log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) + return false, nil // drop envelope without error } - if uint32(envelope.size()) > wh.MaxMessageSize() { + if uint32(envelope.size()) > w.MaxMessageSize() { return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } @@ -598,36 +596,36 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("wrong size of AESNonce: %d bytes [env: %x]", aesNonceSize, envelope.Hash()) } - if envelope.PoW() < wh.MinPow() { + if envelope.PoW() < w.MinPow() { log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex()) return false, nil // drop envelope without error } hash := envelope.Hash() - wh.poolMu.Lock() - _, alreadyCached := wh.envelopes[hash] + w.poolMu.Lock() + _, alreadyCached := w.envelopes[hash] if !alreadyCached { - wh.envelopes[hash] = envelope - if wh.expirations[envelope.Expiry] == nil { - wh.expirations[envelope.Expiry] = set.NewNonTS() + w.envelopes[hash] = envelope + if w.expirations[envelope.Expiry] == nil { + w.expirations[envelope.Expiry] = set.NewNonTS() } - if !wh.expirations[envelope.Expiry].Has(hash) { - wh.expirations[envelope.Expiry].Add(hash) + if !w.expirations[envelope.Expiry].Has(hash) { + w.expirations[envelope.Expiry].Add(hash) } } - wh.poolMu.Unlock() + w.poolMu.Unlock() if alreadyCached { log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex()) } else { log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex()) - wh.statsMu.Lock() - wh.stats.memoryUsed += envelope.size() - wh.statsMu.Unlock() - wh.postEvent(envelope, false) // notify the local node about the new message - if wh.mailServer != nil { - wh.mailServer.Archive(envelope) + w.statsMu.Lock() + w.stats.memoryUsed += envelope.size() + w.statsMu.Unlock() + w.postEvent(envelope, false) // notify the local node about the new message + if w.mailServer != nil { + w.mailServer.Archive(envelope) } } return true, nil @@ -838,9 +836,8 @@ func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error // because it's a once in a session experience derivedKey := pbkdf2.Key(key, nil, 65356, aesKeyLength, sha256.New) return derivedKey, nil - } else { - return nil, unknownVersionError(version) } + return nil, unknownVersionError(version) } // GenerateRandomID generates a random string, which is then returned to be used as a key id diff --git a/whisper/whisperv6/api.go b/whisper/whisperv6/api.go index 3f3a082afe..c60bc46a13 100644 --- a/whisper/whisperv6/api.go +++ b/whisper/whisperv6/api.go @@ -32,10 +32,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -const ( - filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds -) - // List of errors var ( ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") @@ -231,8 +227,9 @@ type newMessageOverride struct { Padding hexutil.Bytes } -// Post a message on the Whisper network. -func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) { +// Post posts a message on the Whisper network. +// returns the hash of the message in case of success. +func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) { var ( symKeyGiven = len(req.SymKeyID) > 0 pubKeyGiven = len(req.PublicKey) > 0 @@ -241,7 +238,7 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, er // user must specify either a symmetric or an asymmetric key if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { - return false, ErrSymAsym + return nil, ErrSymAsym } params := &MessageParams{ @@ -256,20 +253,20 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, er // Set key that is used to sign the message if len(req.Sig) > 0 { if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil { - return false, err + return nil, err } } // Set symmetric key that is used to encrypt the message if symKeyGiven { if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption - return false, ErrNoTopics + return nil, ErrNoTopics } if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { - return false, err + return nil, err } if !validateDataIntegrity(params.KeySym, aesKeyLength) { - return false, ErrInvalidSymmetricKey + return nil, ErrInvalidSymmetricKey } } @@ -277,36 +274,47 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, er if pubKeyGiven { params.Dst = crypto.ToECDSAPub(req.PublicKey) if !ValidatePublicKey(params.Dst) { - return false, ErrInvalidPublicKey + return nil, ErrInvalidPublicKey } } // encrypt and sent message whisperMsg, err := NewSentMessage(params) if err != nil { - return false, err + return nil, err } + var result []byte env, err := whisperMsg.Wrap(params) if err != nil { - return false, err + return nil, err } // send to specific node (skip PoW check) if len(req.TargetPeer) > 0 { n, err := discover.ParseNode(req.TargetPeer) if err != nil { - return false, fmt.Errorf("failed to parse target peer: %s", err) + return nil, fmt.Errorf("failed to parse target peer: %s", err) } - return true, api.w.SendP2PMessage(n.ID[:], env) + err = api.w.SendP2PMessage(n.ID[:], env) + if err == nil { + hash := env.Hash() + result = hash[:] + } + return result, err } // ensure that the message PoW meets the node's minimum accepted PoW if req.PowTarget < api.w.MinPow() { - return false, ErrTooLowPoW + return nil, ErrTooLowPoW } - return true, api.w.Send(env) + err = api.w.Send(env) + if err == nil { + hash := env.Hash() + result = hash[:] + } + return result, err } //go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go