Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
ddadc3d2
Unverified
Commit
ddadc3d2
authored
Jan 20, 2021
by
Péter Szilágyi
Committed by
GitHub
Jan 20, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #21047 from holiman/improve_updates_2
core: improve trie updates (part 2)
parents
81bf9f97
42f9f1f0
Changes
12
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
545 additions
and
81 deletions
+545
-81
simulated.go
accounts/abi/bind/backends/simulated.go
+1
-2
blockchain.go
core/blockchain.go
+9
-6
database.go
core/state/database.go
+10
-2
state_object.go
core/state/state_object.go
+62
-22
state_test.go
core/state/state_test.go
+1
-1
statedb.go
core/state/statedb.go
+86
-38
statedb_test.go
core/state/statedb_test.go
+3
-3
trie_prefetcher.go
core/state/trie_prefetcher.go
+334
-0
crypto.go
crypto/crypto.go
+15
-2
crypto_test.go
crypto/crypto_test.go
+7
-0
api_tracer.go
eth/api_tracer.go
+4
-2
worker.go
miner/worker.go
+13
-3
No files found.
accounts/abi/bind/backends/simulated.go
View file @
ddadc3d2
...
@@ -125,10 +125,9 @@ func (b *SimulatedBackend) Rollback() {
...
@@ -125,10 +125,9 @@ func (b *SimulatedBackend) Rollback() {
func
(
b
*
SimulatedBackend
)
rollback
()
{
func
(
b
*
SimulatedBackend
)
rollback
()
{
blocks
,
_
:=
core
.
GenerateChain
(
b
.
config
,
b
.
blockchain
.
CurrentBlock
(),
ethash
.
NewFaker
(),
b
.
database
,
1
,
func
(
int
,
*
core
.
BlockGen
)
{})
blocks
,
_
:=
core
.
GenerateChain
(
b
.
config
,
b
.
blockchain
.
CurrentBlock
(),
ethash
.
NewFaker
(),
b
.
database
,
1
,
func
(
int
,
*
core
.
BlockGen
)
{})
stateDB
,
_
:=
b
.
blockchain
.
State
()
b
.
pendingBlock
=
blocks
[
0
]
b
.
pendingBlock
=
blocks
[
0
]
b
.
pendingState
,
_
=
state
.
New
(
b
.
pendingBlock
.
Root
(),
stateDB
.
Databas
e
(),
nil
)
b
.
pendingState
,
_
=
state
.
New
(
b
.
pendingBlock
.
Root
(),
b
.
blockchain
.
StateCach
e
(),
nil
)
}
}
// stateByBlockNumber retrieves a state by a given blocknumber.
// stateByBlockNumber retrieves a state by a given blocknumber.
...
...
core/blockchain.go
View file @
ddadc3d2
...
@@ -203,7 +203,7 @@ type BlockChain struct {
...
@@ -203,7 +203,7 @@ type BlockChain struct {
engine
consensus
.
Engine
engine
consensus
.
Engine
validator
Validator
// Block and state validator interface
validator
Validator
// Block and state validator interface
prefetcher
Prefetcher
// Block state prefetcher interface
prefetcher
Prefetcher
processor
Processor
// Block transaction processor interface
processor
Processor
// Block transaction processor interface
vmConfig
vm
.
Config
vmConfig
vm
.
Config
...
@@ -1860,12 +1860,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
...
@@ -1860,12 +1860,17 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
if
err
!=
nil
{
if
err
!=
nil
{
return
it
.
index
,
err
return
it
.
index
,
err
}
}
// Enable prefetching to pull in trie node paths while processing transactions
statedb
.
StartPrefetcher
(
"chain"
)
defer
statedb
.
StopPrefetcher
()
// stopped on write anyway, defer meant to catch early error returns
// If we have a followup block, run that against the current state to pre-cache
// If we have a followup block, run that against the current state to pre-cache
// transactions and probabilistically some of the account/storage trie nodes.
// transactions and probabilistically some of the account/storage trie nodes.
var
followupInterrupt
uint32
var
followupInterrupt
uint32
if
!
bc
.
cacheConfig
.
TrieCleanNoPrefetch
{
if
!
bc
.
cacheConfig
.
TrieCleanNoPrefetch
{
if
followup
,
err
:=
it
.
peek
();
followup
!=
nil
&&
err
==
nil
{
if
followup
,
err
:=
it
.
peek
();
followup
!=
nil
&&
err
==
nil
{
throwaway
,
_
:=
state
.
New
(
parent
.
Root
,
bc
.
stateCache
,
bc
.
snaps
)
throwaway
,
_
:=
state
.
New
(
parent
.
Root
,
bc
.
stateCache
,
bc
.
snaps
)
go
func
(
start
time
.
Time
,
followup
*
types
.
Block
,
throwaway
*
state
.
StateDB
,
interrupt
*
uint32
)
{
go
func
(
start
time
.
Time
,
followup
*
types
.
Block
,
throwaway
*
state
.
StateDB
,
interrupt
*
uint32
)
{
bc
.
prefetcher
.
Prefetch
(
followup
,
throwaway
,
bc
.
vmConfig
,
&
followupInterrupt
)
bc
.
prefetcher
.
Prefetch
(
followup
,
throwaway
,
bc
.
vmConfig
,
&
followupInterrupt
)
...
@@ -1891,7 +1896,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
...
@@ -1891,7 +1896,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
storageUpdateTimer
.
Update
(
statedb
.
StorageUpdates
)
// Storage updates are complete, we can mark them
storageUpdateTimer
.
Update
(
statedb
.
StorageUpdates
)
// Storage updates are complete, we can mark them
snapshotAccountReadTimer
.
Update
(
statedb
.
SnapshotAccountReads
)
// Account reads are complete, we can mark them
snapshotAccountReadTimer
.
Update
(
statedb
.
SnapshotAccountReads
)
// Account reads are complete, we can mark them
snapshotStorageReadTimer
.
Update
(
statedb
.
SnapshotStorageReads
)
// Storage reads are complete, we can mark them
snapshotStorageReadTimer
.
Update
(
statedb
.
SnapshotStorageReads
)
// Storage reads are complete, we can mark them
triehash
:=
statedb
.
AccountHashes
+
statedb
.
StorageHashes
// Save to not double count in validation
triehash
:=
statedb
.
AccountHashes
+
statedb
.
StorageHashes
// Save to not double count in validation
trieproc
:=
statedb
.
SnapshotAccountReads
+
statedb
.
AccountReads
+
statedb
.
AccountUpdates
trieproc
:=
statedb
.
SnapshotAccountReads
+
statedb
.
AccountReads
+
statedb
.
AccountUpdates
trieproc
+=
statedb
.
SnapshotStorageReads
+
statedb
.
StorageReads
+
statedb
.
StorageUpdates
trieproc
+=
statedb
.
SnapshotStorageReads
+
statedb
.
StorageReads
+
statedb
.
StorageUpdates
...
@@ -1920,7 +1924,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
...
@@ -1920,7 +1924,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
if
err
!=
nil
{
if
err
!=
nil
{
return
it
.
index
,
err
return
it
.
index
,
err
}
}
// Update the metrics touched during block commit
// Update the metrics touched during block commit
accountCommitTimer
.
Update
(
statedb
.
AccountCommits
)
// Account commits are complete, we can mark them
accountCommitTimer
.
Update
(
statedb
.
AccountCommits
)
// Account commits are complete, we can mark them
storageCommitTimer
.
Update
(
statedb
.
StorageCommits
)
// Storage commits are complete, we can mark them
storageCommitTimer
.
Update
(
statedb
.
StorageCommits
)
// Storage commits are complete, we can mark them
...
...
core/state/database.go
View file @
ddadc3d2
...
@@ -129,12 +129,20 @@ type cachingDB struct {
...
@@ -129,12 +129,20 @@ type cachingDB struct {
// OpenTrie opens the main account trie at a specific root hash.
// OpenTrie opens the main account trie at a specific root hash.
func
(
db
*
cachingDB
)
OpenTrie
(
root
common
.
Hash
)
(
Trie
,
error
)
{
func
(
db
*
cachingDB
)
OpenTrie
(
root
common
.
Hash
)
(
Trie
,
error
)
{
return
trie
.
NewSecure
(
root
,
db
.
db
)
tr
,
err
:=
trie
.
NewSecure
(
root
,
db
.
db
)
if
err
!=
nil
{
return
nil
,
err
}
return
tr
,
nil
}
}
// OpenStorageTrie opens the storage trie of an account.
// OpenStorageTrie opens the storage trie of an account.
func
(
db
*
cachingDB
)
OpenStorageTrie
(
addrHash
,
root
common
.
Hash
)
(
Trie
,
error
)
{
func
(
db
*
cachingDB
)
OpenStorageTrie
(
addrHash
,
root
common
.
Hash
)
(
Trie
,
error
)
{
return
trie
.
NewSecure
(
root
,
db
.
db
)
tr
,
err
:=
trie
.
NewSecure
(
root
,
db
.
db
)
if
err
!=
nil
{
return
nil
,
err
}
return
tr
,
nil
}
}
// CopyTrie returns an independent copy of the given trie.
// CopyTrie returns an independent copy of the given trie.
...
...
core/state/state_object.go
View file @
ddadc3d2
...
@@ -156,6 +156,14 @@ func (s *stateObject) touch() {
...
@@ -156,6 +156,14 @@ func (s *stateObject) touch() {
}
}
func
(
s
*
stateObject
)
getTrie
(
db
Database
)
Trie
{
func
(
s
*
stateObject
)
getTrie
(
db
Database
)
Trie
{
if
s
.
trie
==
nil
{
// Try fetching from prefetcher first
// We don't prefetch empty tries
if
s
.
data
.
Root
!=
emptyRoot
&&
s
.
db
.
prefetcher
!=
nil
{
// When the miner is creating the pending state, there is no
// prefetcher
s
.
trie
=
s
.
db
.
prefetcher
.
trie
(
s
.
data
.
Root
)
}
if
s
.
trie
==
nil
{
if
s
.
trie
==
nil
{
var
err
error
var
err
error
s
.
trie
,
err
=
db
.
OpenStorageTrie
(
s
.
addrHash
,
s
.
data
.
Root
)
s
.
trie
,
err
=
db
.
OpenStorageTrie
(
s
.
addrHash
,
s
.
data
.
Root
)
...
@@ -164,6 +172,7 @@ func (s *stateObject) getTrie(db Database) Trie {
...
@@ -164,6 +172,7 @@ func (s *stateObject) getTrie(db Database) Trie {
s
.
setError
(
fmt
.
Errorf
(
"can't create storage trie: %v"
,
err
))
s
.
setError
(
fmt
.
Errorf
(
"can't create storage trie: %v"
,
err
))
}
}
}
}
}
return
s
.
trie
return
s
.
trie
}
}
...
@@ -199,10 +208,22 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
...
@@ -199,10 +208,22 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
var
(
var
(
enc
[]
byte
enc
[]
byte
err
error
err
error
meter
*
time
.
Duration
)
)
readStart
:=
time
.
Now
()
if
metrics
.
EnabledExpensive
{
// If the snap is 'under construction', the first lookup may fail. If that
// happens, we don't want to double-count the time elapsed. Thus this
// dance with the metering.
defer
func
()
{
if
meter
!=
nil
{
*
meter
+=
time
.
Since
(
readStart
)
}
}()
}
if
s
.
db
.
snap
!=
nil
{
if
s
.
db
.
snap
!=
nil
{
if
metrics
.
EnabledExpensive
{
if
metrics
.
EnabledExpensive
{
defer
func
(
start
time
.
Time
)
{
s
.
db
.
SnapshotStorageReads
+=
time
.
Since
(
start
)
}(
time
.
Now
())
meter
=
&
s
.
db
.
SnapshotStorageReads
}
}
// If the object was destructed in *this* block (and potentially resurrected),
// If the object was destructed in *this* block (and potentially resurrected),
// the storage has been cleared out, and we should *not* consult the previous
// the storage has been cleared out, and we should *not* consult the previous
...
@@ -217,8 +238,14 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
...
@@ -217,8 +238,14 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has
}
}
// If snapshot unavailable or reading from it failed, load from the database
// If snapshot unavailable or reading from it failed, load from the database
if
s
.
db
.
snap
==
nil
||
err
!=
nil
{
if
s
.
db
.
snap
==
nil
||
err
!=
nil
{
if
meter
!=
nil
{
// If we already spent time checking the snapshot, account for it
// and reset the readStart
*
meter
+=
time
.
Since
(
readStart
)
readStart
=
time
.
Now
()
}
if
metrics
.
EnabledExpensive
{
if
metrics
.
EnabledExpensive
{
defer
func
(
start
time
.
Time
)
{
s
.
db
.
StorageReads
+=
time
.
Since
(
start
)
}(
time
.
Now
())
meter
=
&
s
.
db
.
StorageReads
}
}
if
enc
,
err
=
s
.
getTrie
(
db
)
.
TryGet
(
key
.
Bytes
());
err
!=
nil
{
if
enc
,
err
=
s
.
getTrie
(
db
)
.
TryGet
(
key
.
Bytes
());
err
!=
nil
{
s
.
setError
(
err
)
s
.
setError
(
err
)
...
@@ -282,9 +309,16 @@ func (s *stateObject) setState(key, value common.Hash) {
...
@@ -282,9 +309,16 @@ func (s *stateObject) setState(key, value common.Hash) {
// finalise moves all dirty storage slots into the pending area to be hashed or
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
// committed later. It is invoked at the end of every transaction.
func
(
s
*
stateObject
)
finalise
()
{
func
(
s
*
stateObject
)
finalise
(
prefetch
bool
)
{
slotsToPrefetch
:=
make
([][]
byte
,
0
,
len
(
s
.
dirtyStorage
))
for
key
,
value
:=
range
s
.
dirtyStorage
{
for
key
,
value
:=
range
s
.
dirtyStorage
{
s
.
pendingStorage
[
key
]
=
value
s
.
pendingStorage
[
key
]
=
value
if
value
!=
s
.
originStorage
[
key
]
{
slotsToPrefetch
=
append
(
slotsToPrefetch
,
common
.
CopyBytes
(
key
[
:
]))
// Copy needed for closure
}
}
if
s
.
db
.
prefetcher
!=
nil
&&
prefetch
&&
len
(
slotsToPrefetch
)
>
0
&&
s
.
data
.
Root
!=
emptyRoot
{
s
.
db
.
prefetcher
.
prefetch
(
s
.
data
.
Root
,
slotsToPrefetch
)
}
}
if
len
(
s
.
dirtyStorage
)
>
0
{
if
len
(
s
.
dirtyStorage
)
>
0
{
s
.
dirtyStorage
=
make
(
Storage
)
s
.
dirtyStorage
=
make
(
Storage
)
...
@@ -295,7 +329,7 @@ func (s *stateObject) finalise() {
...
@@ -295,7 +329,7 @@ func (s *stateObject) finalise() {
// It will return nil if the trie has not been loaded and no changes have been made
// It will return nil if the trie has not been loaded and no changes have been made
func
(
s
*
stateObject
)
updateTrie
(
db
Database
)
Trie
{
func
(
s
*
stateObject
)
updateTrie
(
db
Database
)
Trie
{
// Make sure all dirty slots are finalized into the pending storage area
// Make sure all dirty slots are finalized into the pending storage area
s
.
finalise
(
)
s
.
finalise
(
false
)
// Don't prefetch any more, pull directly if need be
if
len
(
s
.
pendingStorage
)
==
0
{
if
len
(
s
.
pendingStorage
)
==
0
{
return
s
.
trie
return
s
.
trie
}
}
...
@@ -303,18 +337,13 @@ func (s *stateObject) updateTrie(db Database) Trie {
...
@@ -303,18 +337,13 @@ func (s *stateObject) updateTrie(db Database) Trie {
if
metrics
.
EnabledExpensive
{
if
metrics
.
EnabledExpensive
{
defer
func
(
start
time
.
Time
)
{
s
.
db
.
StorageUpdates
+=
time
.
Since
(
start
)
}(
time
.
Now
())
defer
func
(
start
time
.
Time
)
{
s
.
db
.
StorageUpdates
+=
time
.
Since
(
start
)
}(
time
.
Now
())
}
}
//
Retrieve t
he snapshot storage map for the object
//
T
he snapshot storage map for the object
var
storage
map
[
common
.
Hash
][]
byte
var
storage
map
[
common
.
Hash
][]
byte
if
s
.
db
.
snap
!=
nil
{
// Retrieve the old storage map, if available, create a new one otherwise
storage
=
s
.
db
.
snapStorage
[
s
.
addrHash
]
if
storage
==
nil
{
storage
=
make
(
map
[
common
.
Hash
][]
byte
)
s
.
db
.
snapStorage
[
s
.
addrHash
]
=
storage
}
}
// Insert all the pending updates into the trie
// Insert all the pending updates into the trie
tr
:=
s
.
getTrie
(
db
)
tr
:=
s
.
getTrie
(
db
)
hasher
:=
s
.
db
.
hasher
usedStorage
:=
make
([][]
byte
,
0
,
len
(
s
.
pendingStorage
))
for
key
,
value
:=
range
s
.
pendingStorage
{
for
key
,
value
:=
range
s
.
pendingStorage
{
// Skip noop changes, persist actual changes
// Skip noop changes, persist actual changes
if
value
==
s
.
originStorage
[
key
]
{
if
value
==
s
.
originStorage
[
key
]
{
...
@@ -331,9 +360,20 @@ func (s *stateObject) updateTrie(db Database) Trie {
...
@@ -331,9 +360,20 @@ func (s *stateObject) updateTrie(db Database) Trie {
s
.
setError
(
tr
.
TryUpdate
(
key
[
:
],
v
))
s
.
setError
(
tr
.
TryUpdate
(
key
[
:
],
v
))
}
}
// If state snapshotting is active, cache the data til commit
// If state snapshotting is active, cache the data til commit
if
storage
!=
nil
{
if
s
.
db
.
snap
!=
nil
{
storage
[
crypto
.
Keccak256Hash
(
key
[
:
])]
=
v
// v will be nil if value is 0x00
if
storage
==
nil
{
// Retrieve the old storage map, if available, create a new one otherwise
if
storage
=
s
.
db
.
snapStorage
[
s
.
addrHash
];
storage
==
nil
{
storage
=
make
(
map
[
common
.
Hash
][]
byte
)
s
.
db
.
snapStorage
[
s
.
addrHash
]
=
storage
}
}
storage
[
crypto
.
HashData
(
hasher
,
key
[
:
])]
=
v
// v will be nil if value is 0x00
}
usedStorage
=
append
(
usedStorage
,
common
.
CopyBytes
(
key
[
:
]))
// Copy needed for closure
}
}
if
s
.
db
.
prefetcher
!=
nil
{
s
.
db
.
prefetcher
.
used
(
s
.
data
.
Root
,
usedStorage
)
}
}
if
len
(
s
.
pendingStorage
)
>
0
{
if
len
(
s
.
pendingStorage
)
>
0
{
s
.
pendingStorage
=
make
(
Storage
)
s
.
pendingStorage
=
make
(
Storage
)
...
...
core/state/state_test.go
View file @
ddadc3d2
...
@@ -170,7 +170,7 @@ func TestSnapshot2(t *testing.T) {
...
@@ -170,7 +170,7 @@ func TestSnapshot2(t *testing.T) {
state
.
setStateObject
(
so0
)
state
.
setStateObject
(
so0
)
root
,
_
:=
state
.
Commit
(
false
)
root
,
_
:=
state
.
Commit
(
false
)
state
.
Reset
(
root
)
state
,
_
=
New
(
root
,
state
.
db
,
state
.
snaps
)
// and one with deleted == true
// and one with deleted == true
so1
:=
state
.
getStateObject
(
stateobjaddr1
)
so1
:=
state
.
getStateObject
(
stateobjaddr1
)
...
...
core/state/statedb.go
View file @
ddadc3d2
...
@@ -63,7 +63,10 @@ func (n *proofList) Delete(key []byte) error {
...
@@ -63,7 +63,10 @@ func (n *proofList) Delete(key []byte) error {
// * Accounts
// * Accounts
type
StateDB
struct
{
type
StateDB
struct
{
db
Database
db
Database
prefetcher
*
triePrefetcher
originalRoot
common
.
Hash
// The pre-state root, before any changes were made
trie
Trie
trie
Trie
hasher
crypto
.
KeccakState
snaps
*
snapshot
.
Tree
snaps
*
snapshot
.
Tree
snap
snapshot
.
Snapshot
snap
snapshot
.
Snapshot
...
@@ -125,6 +128,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
...
@@ -125,6 +128,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
sdb
:=
&
StateDB
{
sdb
:=
&
StateDB
{
db
:
db
,
db
:
db
,
trie
:
tr
,
trie
:
tr
,
originalRoot
:
root
,
snaps
:
snaps
,
snaps
:
snaps
,
stateObjects
:
make
(
map
[
common
.
Address
]
*
stateObject
),
stateObjects
:
make
(
map
[
common
.
Address
]
*
stateObject
),
stateObjectsPending
:
make
(
map
[
common
.
Address
]
struct
{}),
stateObjectsPending
:
make
(
map
[
common
.
Address
]
struct
{}),
...
@@ -133,6 +137,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
...
@@ -133,6 +137,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
preimages
:
make
(
map
[
common
.
Hash
][]
byte
),
preimages
:
make
(
map
[
common
.
Hash
][]
byte
),
journal
:
newJournal
(),
journal
:
newJournal
(),
accessList
:
newAccessList
(),
accessList
:
newAccessList
(),
hasher
:
crypto
.
NewKeccakState
(),
}
}
if
sdb
.
snaps
!=
nil
{
if
sdb
.
snaps
!=
nil
{
if
sdb
.
snap
=
sdb
.
snaps
.
Snapshot
(
root
);
sdb
.
snap
!=
nil
{
if
sdb
.
snap
=
sdb
.
snaps
.
Snapshot
(
root
);
sdb
.
snap
!=
nil
{
...
@@ -144,6 +149,28 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
...
@@ -144,6 +149,28 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
return
sdb
,
nil
return
sdb
,
nil
}
}
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
func
(
s
*
StateDB
)
StartPrefetcher
(
namespace
string
)
{
if
s
.
prefetcher
!=
nil
{
s
.
prefetcher
.
close
()
s
.
prefetcher
=
nil
}
if
s
.
snap
!=
nil
{
s
.
prefetcher
=
newTriePrefetcher
(
s
.
db
,
s
.
originalRoot
,
namespace
)
}
}
// StopPrefetcher terminates a running prefetcher and reports any leftover stats
// from the gathered metrics.
func
(
s
*
StateDB
)
StopPrefetcher
()
{
if
s
.
prefetcher
!=
nil
{
s
.
prefetcher
.
close
()
s
.
prefetcher
=
nil
}
}
// setError remembers the first non-nil error it is called with.
// setError remembers the first non-nil error it is called with.
func
(
s
*
StateDB
)
setError
(
err
error
)
{
func
(
s
*
StateDB
)
setError
(
err
error
)
{
if
s
.
dbErr
==
nil
{
if
s
.
dbErr
==
nil
{
...
@@ -155,37 +182,6 @@ func (s *StateDB) Error() error {
...
@@ -155,37 +182,6 @@ func (s *StateDB) Error() error {
return
s
.
dbErr
return
s
.
dbErr
}
}
// Reset clears out all ephemeral state objects from the state db, but keeps
// the underlying state trie to avoid reloading data for the next operations.
func
(
s
*
StateDB
)
Reset
(
root
common
.
Hash
)
error
{
tr
,
err
:=
s
.
db
.
OpenTrie
(
root
)
if
err
!=
nil
{
return
err
}
s
.
trie
=
tr
s
.
stateObjects
=
make
(
map
[
common
.
Address
]
*
stateObject
)
s
.
stateObjectsPending
=
make
(
map
[
common
.
Address
]
struct
{})
s
.
stateObjectsDirty
=
make
(
map
[
common
.
Address
]
struct
{})
s
.
thash
=
common
.
Hash
{}
s
.
bhash
=
common
.
Hash
{}
s
.
txIndex
=
0
s
.
logs
=
make
(
map
[
common
.
Hash
][]
*
types
.
Log
)
s
.
logSize
=
0
s
.
preimages
=
make
(
map
[
common
.
Hash
][]
byte
)
s
.
clearJournalAndRefund
()
if
s
.
snaps
!=
nil
{
s
.
snapAccounts
,
s
.
snapDestructs
,
s
.
snapStorage
=
nil
,
nil
,
nil
if
s
.
snap
=
s
.
snaps
.
Snapshot
(
root
);
s
.
snap
!=
nil
{
s
.
snapDestructs
=
make
(
map
[
common
.
Hash
]
struct
{})
s
.
snapAccounts
=
make
(
map
[
common
.
Hash
][]
byte
)
s
.
snapStorage
=
make
(
map
[
common
.
Hash
]
map
[
common
.
Hash
][]
byte
)
}
}
s
.
accessList
=
newAccessList
()
return
nil
}
func
(
s
*
StateDB
)
AddLog
(
log
*
types
.
Log
)
{
func
(
s
*
StateDB
)
AddLog
(
log
*
types
.
Log
)
{
s
.
journal
.
append
(
addLogChange
{
txhash
:
s
.
thash
})
s
.
journal
.
append
(
addLogChange
{
txhash
:
s
.
thash
})
...
@@ -532,7 +528,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
...
@@ -532,7 +528,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
defer
func
(
start
time
.
Time
)
{
s
.
SnapshotAccountReads
+=
time
.
Since
(
start
)
}(
time
.
Now
())
defer
func
(
start
time
.
Time
)
{
s
.
SnapshotAccountReads
+=
time
.
Since
(
start
)
}(
time
.
Now
())
}
}
var
acc
*
snapshot
.
Account
var
acc
*
snapshot
.
Account
if
acc
,
err
=
s
.
snap
.
Account
(
crypto
.
Keccak256Hash
(
addr
.
Bytes
()));
err
==
nil
{
if
acc
,
err
=
s
.
snap
.
Account
(
crypto
.
HashData
(
s
.
hasher
,
addr
.
Bytes
()));
err
==
nil
{
if
acc
==
nil
{
if
acc
==
nil
{
return
nil
return
nil
}
}
...
@@ -675,6 +671,7 @@ func (s *StateDB) Copy() *StateDB {
...
@@ -675,6 +671,7 @@ func (s *StateDB) Copy() *StateDB {
logSize
:
s
.
logSize
,
logSize
:
s
.
logSize
,
preimages
:
make
(
map
[
common
.
Hash
][]
byte
,
len
(
s
.
preimages
)),
preimages
:
make
(
map
[
common
.
Hash
][]
byte
,
len
(
s
.
preimages
)),
journal
:
newJournal
(),
journal
:
newJournal
(),
hasher
:
crypto
.
NewKeccakState
(),
}
}
// Copy the dirty states, logs, and preimages
// Copy the dirty states, logs, and preimages
for
addr
:=
range
s
.
journal
.
dirties
{
for
addr
:=
range
s
.
journal
.
dirties
{
...
@@ -724,6 +721,13 @@ func (s *StateDB) Copy() *StateDB {
...
@@ -724,6 +721,13 @@ func (s *StateDB) Copy() *StateDB {
// However, it doesn't cost us much to copy an empty list, so we do it anyway
// However, it doesn't cost us much to copy an empty list, so we do it anyway
// to not blow up if we ever decide copy it in the middle of a transaction
// to not blow up if we ever decide copy it in the middle of a transaction
state
.
accessList
=
s
.
accessList
.
Copy
()
state
.
accessList
=
s
.
accessList
.
Copy
()
// If there's a prefetcher running, make an inactive copy of it that can
// only access data but does not actively preload (since the user will not
// know that they need to explicitly terminate an active copy).
if
s
.
prefetcher
!=
nil
{
state
.
prefetcher
=
s
.
prefetcher
.
copy
()
}
return
state
return
state
}
}
...
@@ -760,6 +764,7 @@ func (s *StateDB) GetRefund() uint64 {
...
@@ -760,6 +764,7 @@ func (s *StateDB) GetRefund() uint64 {
// the journal as well as the refunds. Finalise, however, will not push any updates
// the journal as well as the refunds. Finalise, however, will not push any updates
// into the tries just yet. Only IntermediateRoot or Commit will do that.
// into the tries just yet. Only IntermediateRoot or Commit will do that.
func
(
s
*
StateDB
)
Finalise
(
deleteEmptyObjects
bool
)
{
func
(
s
*
StateDB
)
Finalise
(
deleteEmptyObjects
bool
)
{
addressesToPrefetch
:=
make
([][]
byte
,
0
,
len
(
s
.
journal
.
dirties
))
for
addr
:=
range
s
.
journal
.
dirties
{
for
addr
:=
range
s
.
journal
.
dirties
{
obj
,
exist
:=
s
.
stateObjects
[
addr
]
obj
,
exist
:=
s
.
stateObjects
[
addr
]
if
!
exist
{
if
!
exist
{
...
@@ -784,10 +789,18 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
...
@@ -784,10 +789,18 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
delete
(
s
.
snapStorage
,
obj
.
addrHash
)
// Clear out any previously updated storage data (may be recreated via a ressurrect)
delete
(
s
.
snapStorage
,
obj
.
addrHash
)
// Clear out any previously updated storage data (may be recreated via a ressurrect)
}
}
}
else
{
}
else
{
obj
.
finalise
(
)
obj
.
finalise
(
true
)
// Prefetch slots in the background
}
}
s
.
stateObjectsPending
[
addr
]
=
struct
{}{}
s
.
stateObjectsPending
[
addr
]
=
struct
{}{}
s
.
stateObjectsDirty
[
addr
]
=
struct
{}{}
s
.
stateObjectsDirty
[
addr
]
=
struct
{}{}
// At this point, also ship the address off to the precacher. The precacher
// will start loading tries, and when the change is eventually committed,
// the commit-phase will be a lot faster
addressesToPrefetch
=
append
(
addressesToPrefetch
,
common
.
CopyBytes
(
addr
[
:
]))
// Copy needed for closure
}
if
s
.
prefetcher
!=
nil
&&
len
(
addressesToPrefetch
)
>
0
{
s
.
prefetcher
.
prefetch
(
s
.
originalRoot
,
addressesToPrefetch
)
}
}
// Invalidate journal because reverting across transactions is not allowed.
// Invalidate journal because reverting across transactions is not allowed.
s
.
clearJournalAndRefund
()
s
.
clearJournalAndRefund
()
...
@@ -800,14 +813,49 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
...
@@ -800,14 +813,49 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Finalise all the dirty storage states and write them into the tries
// Finalise all the dirty storage states and write them into the tries
s
.
Finalise
(
deleteEmptyObjects
)
s
.
Finalise
(
deleteEmptyObjects
)
// If there was a trie prefetcher operating, it gets aborted and irrevocably
// modified after we start retrieving tries. Remove it from the statedb after
// this round of use.
//
// This is weird pre-byzantium since the first tx runs with a prefetcher and
// the remainder without, but pre-byzantium even the initial prefetcher is
// useless, so no sleep lost.
prefetcher
:=
s
.
prefetcher
if
s
.
prefetcher
!=
nil
{
defer
func
()
{
s
.
prefetcher
.
close
()
s
.
prefetcher
=
nil
}()
}
// Although naively it makes sense to retrieve the account trie and then do
// the contract storage and account updates sequentially, that short circuits
// the account prefetcher. Instead, let's process all the storage updates
// first, giving the account prefeches just a few more milliseconds of time
// to pull useful data from disk.
for
addr
:=
range
s
.
stateObjectsPending
{
for
addr
:=
range
s
.
stateObjectsPending
{
obj
:=
s
.
stateObjects
[
addr
]
if
obj
:=
s
.
stateObjects
[
addr
];
!
obj
.
deleted
{
if
obj
.
deleted
{
obj
.
updateRoot
(
s
.
db
)
}
}
// Now we're about to start to write changes to the trie. The trie is so far
// _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it.
if
prefetcher
!=
nil
{
if
trie
:=
prefetcher
.
trie
(
s
.
originalRoot
);
trie
!=
nil
{
s
.
trie
=
trie
}
}
usedAddrs
:=
make
([][]
byte
,
0
,
len
(
s
.
stateObjectsPending
))
for
addr
:=
range
s
.
stateObjectsPending
{
if
obj
:=
s
.
stateObjects
[
addr
];
obj
.
deleted
{
s
.
deleteStateObject
(
obj
)
s
.
deleteStateObject
(
obj
)
}
else
{
}
else
{
obj
.
updateRoot
(
s
.
db
)
s
.
updateStateObject
(
obj
)
s
.
updateStateObject
(
obj
)
}
}
usedAddrs
=
append
(
usedAddrs
,
common
.
CopyBytes
(
addr
[
:
]))
// Copy needed for closure
}
if
prefetcher
!=
nil
{
prefetcher
.
used
(
s
.
originalRoot
,
usedAddrs
)
}
}
if
len
(
s
.
stateObjectsPending
)
>
0
{
if
len
(
s
.
stateObjectsPending
)
>
0
{
s
.
stateObjectsPending
=
make
(
map
[
common
.
Address
]
struct
{})
s
.
stateObjectsPending
=
make
(
map
[
common
.
Address
]
struct
{})
...
...
core/state/statedb_test.go
View file @
ddadc3d2
...
@@ -474,7 +474,7 @@ func TestTouchDelete(t *testing.T) {
...
@@ -474,7 +474,7 @@ func TestTouchDelete(t *testing.T) {
s
:=
newStateTest
()
s
:=
newStateTest
()
s
.
state
.
GetOrNewStateObject
(
common
.
Address
{})
s
.
state
.
GetOrNewStateObject
(
common
.
Address
{})
root
,
_
:=
s
.
state
.
Commit
(
false
)
root
,
_
:=
s
.
state
.
Commit
(
false
)
s
.
state
.
Reset
(
root
)
s
.
state
,
_
=
New
(
root
,
s
.
state
.
db
,
s
.
state
.
snaps
)
snapshot
:=
s
.
state
.
Snapshot
()
snapshot
:=
s
.
state
.
Snapshot
()
s
.
state
.
AddBalance
(
common
.
Address
{},
new
(
big
.
Int
))
s
.
state
.
AddBalance
(
common
.
Address
{},
new
(
big
.
Int
))
...
@@ -676,7 +676,7 @@ func TestDeleteCreateRevert(t *testing.T) {
...
@@ -676,7 +676,7 @@ func TestDeleteCreateRevert(t *testing.T) {
state
.
SetBalance
(
addr
,
big
.
NewInt
(
1
))
state
.
SetBalance
(
addr
,
big
.
NewInt
(
1
))
root
,
_
:=
state
.
Commit
(
false
)
root
,
_
:=
state
.
Commit
(
false
)
state
.
Reset
(
root
)
state
,
_
=
New
(
root
,
state
.
db
,
state
.
snaps
)
// Simulate self-destructing in one transaction, then create-reverting in another
// Simulate self-destructing in one transaction, then create-reverting in another
state
.
Suicide
(
addr
)
state
.
Suicide
(
addr
)
...
@@ -688,7 +688,7 @@ func TestDeleteCreateRevert(t *testing.T) {
...
@@ -688,7 +688,7 @@ func TestDeleteCreateRevert(t *testing.T) {
// Commit the entire state and make sure we don't crash and have the correct state
// Commit the entire state and make sure we don't crash and have the correct state
root
,
_
=
state
.
Commit
(
true
)
root
,
_
=
state
.
Commit
(
true
)
state
.
Reset
(
root
)
state
,
_
=
New
(
root
,
state
.
db
,
state
.
snaps
)
if
state
.
getStateObject
(
addr
)
!=
nil
{
if
state
.
getStateObject
(
addr
)
!=
nil
{
t
.
Fatalf
(
"self-destructed contract came alive"
)
t
.
Fatalf
(
"self-destructed contract came alive"
)
...
...
core/state/trie_prefetcher.go
0 → 100644
View file @
ddadc3d2
This diff is collapsed.
Click to expand it.
crypto/crypto.go
View file @
ddadc3d2
...
@@ -60,10 +60,23 @@ type KeccakState interface {
...
@@ -60,10 +60,23 @@ type KeccakState interface {
Read
([]
byte
)
(
int
,
error
)
Read
([]
byte
)
(
int
,
error
)
}
}
// NewKeccakState creates a new KeccakState
func
NewKeccakState
()
KeccakState
{
return
sha3
.
NewLegacyKeccak256
()
.
(
KeccakState
)
}
// HashData hashes the provided data using the KeccakState and returns a 32 byte hash
func
HashData
(
kh
KeccakState
,
data
[]
byte
)
(
h
common
.
Hash
)
{
kh
.
Reset
()
kh
.
Write
(
data
)
kh
.
Read
(
h
[
:
])
return
h
}
// Keccak256 calculates and returns the Keccak256 hash of the input data.
// Keccak256 calculates and returns the Keccak256 hash of the input data.
func
Keccak256
(
data
...
[]
byte
)
[]
byte
{
func
Keccak256
(
data
...
[]
byte
)
[]
byte
{
b
:=
make
([]
byte
,
32
)
b
:=
make
([]
byte
,
32
)
d
:=
sha3
.
NewLegacyKeccak256
()
.
(
KeccakState
)
d
:=
NewKeccakState
(
)
for
_
,
b
:=
range
data
{
for
_
,
b
:=
range
data
{
d
.
Write
(
b
)
d
.
Write
(
b
)
}
}
...
@@ -74,7 +87,7 @@ func Keccak256(data ...[]byte) []byte {
...
@@ -74,7 +87,7 @@ func Keccak256(data ...[]byte) []byte {
// Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// Keccak256Hash calculates and returns the Keccak256 hash of the input data,
// converting it to an internal Hash data structure.
// converting it to an internal Hash data structure.
func
Keccak256Hash
(
data
...
[]
byte
)
(
h
common
.
Hash
)
{
func
Keccak256Hash
(
data
...
[]
byte
)
(
h
common
.
Hash
)
{
d
:=
sha3
.
NewLegacyKeccak256
()
.
(
KeccakState
)
d
:=
NewKeccakState
(
)
for
_
,
b
:=
range
data
{
for
_
,
b
:=
range
data
{
d
.
Write
(
b
)
d
.
Write
(
b
)
}
}
...
...
crypto/crypto_test.go
View file @
ddadc3d2
...
@@ -42,6 +42,13 @@ func TestKeccak256Hash(t *testing.T) {
...
@@ -42,6 +42,13 @@ func TestKeccak256Hash(t *testing.T) {
checkhash
(
t
,
"Sha3-256-array"
,
func
(
in
[]
byte
)
[]
byte
{
h
:=
Keccak256Hash
(
in
);
return
h
[
:
]
},
msg
,
exp
)
checkhash
(
t
,
"Sha3-256-array"
,
func
(
in
[]
byte
)
[]
byte
{
h
:=
Keccak256Hash
(
in
);
return
h
[
:
]
},
msg
,
exp
)
}
}
func
TestKeccak256Hasher
(
t
*
testing
.
T
)
{
msg
:=
[]
byte
(
"abc"
)
exp
,
_
:=
hex
.
DecodeString
(
"4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45"
)
hasher
:=
NewKeccakState
()
checkhash
(
t
,
"Sha3-256-array"
,
func
(
in
[]
byte
)
[]
byte
{
h
:=
HashData
(
hasher
,
in
);
return
h
[
:
]
},
msg
,
exp
)
}
func
TestToECDSAErrors
(
t
*
testing
.
T
)
{
func
TestToECDSAErrors
(
t
*
testing
.
T
)
{
if
_
,
err
:=
HexToECDSA
(
"0000000000000000000000000000000000000000000000000000000000000000"
);
err
==
nil
{
if
_
,
err
:=
HexToECDSA
(
"0000000000000000000000000000000000000000000000000000000000000000"
);
err
==
nil
{
t
.
Fatal
(
"HexToECDSA should've returned error"
)
t
.
Fatal
(
"HexToECDSA should've returned error"
)
...
...
eth/api_tracer.go
View file @
ddadc3d2
...
@@ -299,7 +299,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
...
@@ -299,7 +299,8 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl
failed
=
err
failed
=
err
break
break
}
}
if
err
:=
statedb
.
Reset
(
root
);
err
!=
nil
{
statedb
,
err
=
state
.
New
(
root
,
database
,
nil
)
if
err
!=
nil
{
failed
=
err
failed
=
err
break
break
}
}
...
@@ -699,7 +700,8 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
...
@@ -699,7 +700,8 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
if
err
:=
statedb
.
Reset
(
root
);
err
!=
nil
{
statedb
,
err
=
state
.
New
(
root
,
database
,
nil
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"state reset after block %d failed: %v"
,
block
.
NumberU64
(),
err
)
return
nil
,
fmt
.
Errorf
(
"state reset after block %d failed: %v"
,
block
.
NumberU64
(),
err
)
}
}
database
.
TrieDB
()
.
Reference
(
root
,
common
.
Hash
{})
database
.
TrieDB
()
.
Reference
(
root
,
common
.
Hash
{})
...
...
miner/worker.go
View file @
ddadc3d2
...
@@ -303,6 +303,9 @@ func (w *worker) isRunning() bool {
...
@@ -303,6 +303,9 @@ func (w *worker) isRunning() bool {
// close terminates all background threads maintained by the worker.
// close terminates all background threads maintained by the worker.
// Note the worker does not support being closed multiple times.
// Note the worker does not support being closed multiple times.
func
(
w
*
worker
)
close
()
{
func
(
w
*
worker
)
close
()
{
if
w
.
current
!=
nil
&&
w
.
current
.
state
!=
nil
{
w
.
current
.
state
.
StopPrefetcher
()
}
atomic
.
StoreInt32
(
&
w
.
running
,
0
)
atomic
.
StoreInt32
(
&
w
.
running
,
0
)
close
(
w
.
exitCh
)
close
(
w
.
exitCh
)
}
}
...
@@ -642,10 +645,14 @@ func (w *worker) resultLoop() {
...
@@ -642,10 +645,14 @@ func (w *worker) resultLoop() {
// makeCurrent creates a new environment for the current cycle.
// makeCurrent creates a new environment for the current cycle.
func
(
w
*
worker
)
makeCurrent
(
parent
*
types
.
Block
,
header
*
types
.
Header
)
error
{
func
(
w
*
worker
)
makeCurrent
(
parent
*
types
.
Block
,
header
*
types
.
Header
)
error
{
// Retrieve the parent state to execute on top and start a prefetcher for
// the miner to speed block sealing up a bit
state
,
err
:=
w
.
chain
.
StateAt
(
parent
.
Root
())
state
,
err
:=
w
.
chain
.
StateAt
(
parent
.
Root
())
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
state
.
StartPrefetcher
(
"miner"
)
env
:=
&
environment
{
env
:=
&
environment
{
signer
:
types
.
NewEIP155Signer
(
w
.
chainConfig
.
ChainID
),
signer
:
types
.
NewEIP155Signer
(
w
.
chainConfig
.
ChainID
),
state
:
state
,
state
:
state
,
...
@@ -654,7 +661,6 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
...
@@ -654,7 +661,6 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
uncles
:
mapset
.
NewSet
(),
uncles
:
mapset
.
NewSet
(),
header
:
header
,
header
:
header
,
}
}
// when 08 is processed ancestors contain 07 (quick block)
// when 08 is processed ancestors contain 07 (quick block)
for
_
,
ancestor
:=
range
w
.
chain
.
GetBlocksFromHash
(
parent
.
Hash
(),
7
)
{
for
_
,
ancestor
:=
range
w
.
chain
.
GetBlocksFromHash
(
parent
.
Hash
(),
7
)
{
for
_
,
uncle
:=
range
ancestor
.
Uncles
()
{
for
_
,
uncle
:=
range
ancestor
.
Uncles
()
{
...
@@ -663,9 +669,14 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
...
@@ -663,9 +669,14 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
env
.
family
.
Add
(
ancestor
.
Hash
())
env
.
family
.
Add
(
ancestor
.
Hash
())
env
.
ancestors
.
Add
(
ancestor
.
Hash
())
env
.
ancestors
.
Add
(
ancestor
.
Hash
())
}
}
// Keep track of transactions which return errors so they can be removed
// Keep track of transactions which return errors so they can be removed
env
.
tcount
=
0
env
.
tcount
=
0
// Swap out the old work with the new one, terminating any leftover prefetcher
// processes in the mean time and starting a new one.
if
w
.
current
!=
nil
&&
w
.
current
.
state
!=
nil
{
w
.
current
.
state
.
StopPrefetcher
()
}
w
.
current
=
env
w
.
current
=
env
return
nil
return
nil
}
}
...
@@ -719,7 +730,6 @@ func (w *worker) updateSnapshot() {
...
@@ -719,7 +730,6 @@ func (w *worker) updateSnapshot() {
w
.
current
.
receipts
,
w
.
current
.
receipts
,
new
(
trie
.
Trie
),
new
(
trie
.
Trie
),
)
)
w
.
snapshotState
=
w
.
current
.
state
.
Copy
()
w
.
snapshotState
=
w
.
current
.
state
.
Copy
()
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment