Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feat/improve-web-api
Browse files Browse the repository at this point in the history
  • Loading branch information
karimodm committed Apr 20, 2023
2 parents ba45d82 + 7779969 commit 1e6997a
Show file tree
Hide file tree
Showing 92 changed files with 1,802 additions and 778 deletions.
26 changes: 26 additions & 0 deletions client/message.go → client/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const (
routeBlock = "blocks/"
routeBlockMetadata = "/metadata"
routeSendPayload = "blocks/payload"
routeSendBlock = "blocks"
routeGetReferences = "blocks/references"
)

// GetBlock is the handler for the /blocks/:blockID endpoint.
Expand Down Expand Up @@ -57,3 +59,27 @@ func (api *GoShimmerAPI) SendPayload(payload []byte) (string, error) {

return res.ID, nil
}

// SendBlock sends a block provided in form of bytes.
func (api *GoShimmerAPI) SendBlock(blockBytes []byte) (string, error) {
res := &jsonmodels.PostBlockResponse{}
if err := api.do(http.MethodPost, routeSendBlock,
&jsonmodels.PostBlockRequest{BlockBytes: blockBytes}, res); err != nil {
return "", err
}
return res.BlockID, nil
}

// GetReferences returns the parent references selected by the node for a given payload.
func (api *GoShimmerAPI) GetReferences(payload []byte, parentsCount int) (resp *jsonmodels.GetReferencesResponse, err error) {
res := &jsonmodels.GetReferencesResponse{}
if err := api.do(http.MethodGet, routeGetReferences,
&jsonmodels.GetReferencesRequest{
PayloadBytes: payload,
ParentsCount: parentsCount,
}, res); err != nil {
return nil, err
}

return res, nil
}
76 changes: 76 additions & 0 deletions client/evilspammer/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package evilspammer

import (
"time"

"github.com/iotaledger/goshimmer/client/evilwallet"
"github.com/iotaledger/hive.go/core/slot"
)

// region ClockSync ///////////////////////////////////////////////////////////////////////////////////////////////

// ClockSync is used to synchronize with connected nodes.
type ClockSync struct {
LatestCommittedSlotClock *SlotClock

syncTicker *time.Ticker
clt evilwallet.Client
}

func NewClockSync(slotDuration time.Duration, syncInterval time.Duration, clientList evilwallet.Client) *ClockSync {
updateTicker := time.NewTicker(syncInterval)
return &ClockSync{
LatestCommittedSlotClock: &SlotClock{slotDuration: slotDuration},

syncTicker: updateTicker,
clt: clientList,
}
}

// Start starts the clock synchronization in the background after the first sync is done..
func (c *ClockSync) Start() {
c.Synchronize()
go func() {
for range c.syncTicker.C {
c.Synchronize()
}
}()
}

func (c *ClockSync) Shutdown() {
c.syncTicker.Stop()
}

func (c *ClockSync) Synchronize() {
si, err := c.clt.GetLatestCommittedSlot()
if err != nil {
return
}
c.LatestCommittedSlotClock.Update(si)
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////

// region SlotClock ///////////////////////////////////////////////////////////////////////////////////////////////

type SlotClock struct {
lastUpdated time.Time
updatedSlot slot.Index

slotDuration time.Duration
}

func (c *SlotClock) Update(value slot.Index) {
c.lastUpdated = time.Now()
c.updatedSlot = value
}

func (c *SlotClock) Get() slot.Index {
return c.updatedSlot
}

func (c *SlotClock) GetRelative() slot.Index {
return c.updatedSlot + slot.Index(time.Since(c.lastUpdated)/c.slotDuration)
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////
245 changes: 245 additions & 0 deletions client/evilspammer/commitmentmanager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
package evilspammer

import (
"crypto/sha256"
"math/rand"
"time"

"github.com/iotaledger/goshimmer/client/evilwallet"
"github.com/iotaledger/goshimmer/packages/core/commitment"
"github.com/iotaledger/hive.go/core/slot"

"github.com/pkg/errors"
)

type CommitmentManagerParams struct {
CommitmentType string
ValidClientURL string
ParentRefsCount int
ClockResyncTime time.Duration
GenesisTime time.Time
SlotDuration time.Duration

OptionalForkAfter int
}
type CommitmentManager struct {
Params *CommitmentManagerParams
// we store here only the valid commitments to not request them again through API
validChain map[slot.Index]*commitment.Commitment
// commitments used to spam
commitmentChain map[slot.Index]*commitment.Commitment

initiationSlot slot.Index
forkIndex slot.Index
latestCommitted slot.Index

clockSync *ClockSync
validClient evilwallet.Client

log Logger
}

func NewCommitmentManager() *CommitmentManager {
return &CommitmentManager{
Params: &CommitmentManagerParams{
ParentRefsCount: 2,
ClockResyncTime: 30 * time.Second,
GenesisTime: time.Now(),
SlotDuration: 5 * time.Second,
},
validChain: make(map[slot.Index]*commitment.Commitment),
commitmentChain: make(map[slot.Index]*commitment.Commitment),
}
}

func (c *CommitmentManager) Setup(l Logger) {
c.log = l

c.log.Infof("Commitment Manager will be based on the valid client: %s", c.Params.ValidClientURL)
c.validClient = evilwallet.NewWebClient(c.Params.ValidClientURL)
c.setupTimeParams(c.validClient)

c.clockSync = NewClockSync(c.Params.SlotDuration, c.Params.ClockResyncTime, c.validClient)
c.clockSync.Start()

c.setupForkingPoint()
c.setupInitCommitment()
}

// SetupInitCommitment sets the initiation commitment which is the current valid commitment requested from validClient.
func (c *CommitmentManager) setupInitCommitment() {
c.initiationSlot = c.clockSync.LatestCommittedSlotClock.Get()
comm, err := c.getValidCommitment(c.initiationSlot)
if err != nil {
panic(errors.Wrapf(err, "failed to get initiation commitment"))
}
c.commitmentChain[comm.Index()] = comm
c.latestCommitted = comm.Index()
}

// SetupTimeParams requests through API and sets the genesis time and slot duration for the commitment manager.
func (c *CommitmentManager) setupTimeParams(clt evilwallet.Client) {
genesisTime, slotDuration, err := clt.GetTimeProvider()
if err != nil {
panic(errors.Wrapf(err, "failed to get time provider for the commitment manager setup"))
}
c.Params.GenesisTime = genesisTime
c.Params.SlotDuration = slotDuration
}

func (c *CommitmentManager) SetCommitmentType(commitmentType string) {
c.Params.CommitmentType = commitmentType
}

func (c *CommitmentManager) SetForkAfter(forkAfter int) {
c.Params.OptionalForkAfter = forkAfter
}

// SetupForkingPoint sets the forking point for the commitment manager. It uses ForkAfter parameter so need to be called after params are read.
func (c *CommitmentManager) setupForkingPoint() {
c.forkIndex = c.clockSync.LatestCommittedSlotClock.Get() + slot.Index(c.Params.OptionalForkAfter)
}

func (c *CommitmentManager) Shutdown() {
c.clockSync.Shutdown()
}

func (c *CommitmentManager) commit(comm *commitment.Commitment) {
c.commitmentChain[comm.Index()] = comm
if comm.Index() > c.latestCommitted {
if comm.Index()-c.latestCommitted != 1 {
panic("next committed slot is not sequential, lastCommitted: " + c.latestCommitted.String() + " nextCommitted: " + comm.Index().String())
}
c.latestCommitted = comm.Index()
}
}

func (c *CommitmentManager) getLatestCommitment() *commitment.Commitment {
return c.commitmentChain[c.latestCommitted]
}

// GenerateCommitment generates a commitment based on the commitment type provided in spam details.
func (c *CommitmentManager) GenerateCommitment(clt evilwallet.Client) (*commitment.Commitment, slot.Index, error) {
switch c.Params.CommitmentType {
// todo refactor this to work with chainsA
case "latest":
comm, err := clt.GetLatestCommitment()
if err != nil {
return nil, 0, errors.Wrap(err, "failed to get latest commitment")
}
index, err := clt.GetLatestConfirmedIndex()
if err != nil {
return nil, 0, errors.Wrap(err, "failed to get latest confirmed index")
}
return comm, index, err
case "random":
slotIndex := c.clockSync.LatestCommittedSlotClock.Get()
newCommitment := randomCommitmentChain(slotIndex)

return newCommitment, slotIndex - 10, nil

case "fork":
// it should request time periodically, and be relative
slotIndex := c.clockSync.LatestCommittedSlotClock.Get()
// make sure chain is upto date to the forking point
uptoSlot := c.forkIndex
// get minimum
if slotIndex < c.forkIndex {
uptoSlot = slotIndex
}
err := c.updateChainWithValidCommitment(uptoSlot)
if err != nil {
return nil, 0, errors.Wrap(err, "failed to update chain with valid commitment")
}
if c.isAfterForkPoint(slotIndex) {
c.updateForkedChain(slotIndex)
}
comm := c.getLatestCommitment()
index, err := clt.GetLatestConfirmedIndex()
if err != nil {
return nil, 0, errors.Wrap(err, "failed to get latest confirmed index")
}
return comm, index - 1, nil
}
return nil, 0, nil
}

func (c *CommitmentManager) isAfterForkPoint(slotIndex slot.Index) bool {
return c.forkIndex != 0 && slotIndex > c.forkIndex
}

// updateChainWithValidCommitment commits the chain up to the given slot with the valid commitments.
func (c *CommitmentManager) updateChainWithValidCommitment(s slot.Index) error {
for i := c.latestCommitted + 1; i <= s; i++ {
comm, err := c.getValidCommitment(i)
if err != nil {
return errors.Wrapf(err, "failed to get valid commitment for slot %d", i)
}
c.commit(comm)
}
return nil
}

func (c *CommitmentManager) updateForkedChain(slotIndex slot.Index) {
for i := c.latestCommitted + 1; i <= slotIndex; i++ {
comm, err := c.getForkedCommitment(i)
if err != nil {
panic(errors.Wrapf(err, "failed to get forked commitment for slot %d", i))
}
c.commit(comm)
}
}

// getValidCommitment returns the valid commitment for the given slot if not exists it requests it from the node and update the validChain.
func (c *CommitmentManager) getValidCommitment(slot slot.Index) (*commitment.Commitment, error) {
if comm, ok := c.validChain[slot]; ok {
return comm, nil
}
// if not requested before then get it from the node
comm, err := c.validClient.GetCommitment(int(slot))
if err != nil {
return nil, errors.Wrapf(err, "failed to get commitment for slot %d", slot)
}
c.validChain[slot] = comm

return comm, nil
}

func (c *CommitmentManager) getForkedCommitment(slot slot.Index) (*commitment.Commitment, error) {
validComm, err := c.getValidCommitment(slot)
if err != nil {
return nil, errors.Wrapf(err, "failed to get valid commitment for slot %d", slot)
}
prevComm := c.commitmentChain[slot-1]
forkedComm := commitment.New(
validComm.Index(),
prevComm.ID(),
randomRoot(),
validComm.CumulativeWeight(),
)
return forkedComm, nil
}

func randomCommitmentChain(currSlot slot.Index) *commitment.Commitment {
chain := make([]*commitment.Commitment, currSlot+1)
chain[0] = commitment.NewEmptyCommitment()
for i := slot.Index(0); i < currSlot-1; i++ {
prevComm := chain[i]
newCommitment := commitment.New(
i,
prevComm.ID(),
randomRoot(),
100,
)
chain[i+1] = newCommitment
}
return chain[currSlot-1]
}

func randomRoot() [32]byte {
data := make([]byte, 10)
for i := range data {
data[i] = byte(rand.Intn(256))
}
return sha256.Sum256(data)
}
3 changes: 2 additions & 1 deletion client/evilspammer/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
var (
ErrFailPostTransaction = errors.New("failed to post transaction")
ErrFailSendDataBlock = errors.New("failed to send a data block")

ErrFailGetReferences = errors.New("failed to get references")
ErrTransactionIsNil = errors.New("provided transaction is nil")
ErrFailToPrepareBatch = errors.New("custom conflict batch could not be prepared")
ErrInsufficientClients = errors.New("insufficient clients to send conflicts")
ErrInputsNotSolid = errors.New("not all inputs are solid")
ErrFailPrepareBlock = errors.New("failed to prepare block")
)

// ErrorCounter counts errors that appeared during the spam,
Expand Down
Loading

0 comments on commit 1e6997a

Please sign in to comment.