Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relayers OFAC #1313

Merged
merged 20 commits into from
Oct 19, 2024
Merged
33 changes: 33 additions & 0 deletions relayer/chain/parachain/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package parachain

import (
"encoding/hex"
"fmt"
"strings"

"github.com/decred/base58"
"golang.org/x/crypto/blake2b"
)

func SS58Encode(pubKeyHex string, ss58Prefix uint8) (string, error) {
if strings.HasPrefix(pubKeyHex, "0x") {
pubKeyHex = pubKeyHex[2:]
}

pubKey, err := hex.DecodeString(pubKeyHex)
if err != nil {
return "", fmt.Errorf("failed to decode hex: %w", err)
}

address := append([]byte{ss58Prefix}, pubKey...)

hashInput := append([]byte("SS58PRE"), address...)

hash := blake2b.Sum512(hashInput)
checksum := hash[:2]

fullAddress := append(address, checksum...)

ss58Addr := base58.Encode(fullAddress)
return ss58Addr, nil
}
14 changes: 14 additions & 0 deletions relayer/chain/parachain/address_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package parachain

import (
assert "github.com/stretchr/testify/require"
"testing"
)

func TestSS58Prefix(t *testing.T) {
address := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"

ss58Address, err := SS58Encode(address, 1)
assert.NoError(t, err)
assert.Equal(t, "A1k3praCLftTgBTb6aVavh3UNKwXN599Fqov17MkEy6bwCU", ss58Address)
}
112 changes: 112 additions & 0 deletions relayer/chain/parachain/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package parachain

import (
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -125,3 +126,114 @@ func removeLeadingZeroHashForSlice(s []string) []string {
func removeLeadingZeroHash(s string) string {
return strings.Replace(s, "0x", "", 1)
}

type Destination struct {
Variant types.U8
DestinationBytes types.Data
}

type ForeignAccountId32 struct {
ParaID uint32
ID types.H256
Fee types.U128
}

type ForeignAccountId20 struct {
ParaID uint32
ID types.H160
Fee types.U128
}

type RegisterToken struct {
Token types.H160
Fee types.U128
}

type SendToken struct {
Token types.H160
Destination Destination
}

type SendNativeToken struct {
TokenID types.H256
Destination Destination
}

type InboundMessage struct {
Version types.U8
ChainID types.U64
Command types.U8
CommandBytes types.Data
}

func GetDestination(input []byte) (string, error) {
var inboundMessage = &InboundMessage{}
err := types.DecodeFromBytes(input, inboundMessage)
if err != nil {
return "", fmt.Errorf("failed to decode message: %v", err)
}

address := ""
switch inboundMessage.Command {
case 0:
// Register token does not have a destination
break
case 1:
// Send token has a destination
var command = &SendToken{}
err = types.DecodeFromBytes(inboundMessage.CommandBytes, command)
if err != nil {
return "", fmt.Errorf("failed to decode send token command: %v", err)
}

address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes)
if err != nil {
return "", fmt.Errorf("decode destination: %v", err)
}
case 2:
// Send native token has a destination
var command = &SendNativeToken{}
err = types.DecodeFromBytes(inboundMessage.CommandBytes, command)
if err != nil {
return "", fmt.Errorf("failed to decode send native token command: %v", err)
}

address, err = decodeDestination(command.Destination.Variant, command.Destination.DestinationBytes)
if err != nil {
return "", fmt.Errorf("decode destination: %v", err)
}
}

return address, nil
}

func decodeDestination(variant types.U8, destinationBytes []byte) (string, error) {
switch variant {
case 0:
// Account32
account32 := &types.H256{}
err := types.DecodeFromBytes(destinationBytes, account32)
if err != nil {
return "", fmt.Errorf("failed to decode destination: %v", err)
}
return account32.Hex(), nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in the UI we do not post the hex version of the account. We post the SS58 version. Not sure what the correct thing to do there is?

Copy link
Contributor Author

@claravanstaden claravanstaden Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just saw here Chainalysis does not support non-EVM chains. 😬 https://go.chainalysis.com/chainalysis-oracle-docs.html Compatible networks Edit: Nevermind, I see this is not the API but an oracle.

Checking to see which Polkadot addresses they support...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SS58 version is probably right, will add it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 1e9558e.

case 1:
// Account32 on destination parachain
var account = &ForeignAccountId32{}
err := types.DecodeFromBytes(destinationBytes, account)
if err != nil {
return "", fmt.Errorf("failed to decode foreign account: %v", err)
}
return account.ID.Hex(), nil
case 2:
// Account20
var account = &ForeignAccountId20{}
err := types.DecodeFromBytes(destinationBytes, account)
if err != nil {
return "", fmt.Errorf("failed to decode foreign account: %v", err)
}
return account.ID.Hex(), nil
}

return "", errors.New("destination variant could not be matched")
}
33 changes: 33 additions & 0 deletions relayer/chain/parachain/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package parachain

import (
"testing"

gethCommon "github.com/ethereum/go-ethereum/common"
assert "github.com/stretchr/testify/require"
)

func TestGetDestination(t *testing.T) {
registerTokenPayload := "00a736aa000000000000774667629726ec1fabebcec0d9139bd1c8f72a2300e87648170000000000000000000000"
decodePayloadAndCompareDestinationAddress(t, registerTokenPayload, "") // register token does not have a destination

sendTokenPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a23008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4800c16ff2862300000000000000000000e87648170000000000000000000000"
bobAddress := "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48"
decodePayloadAndCompareDestinationAddress(t, sendTokenPayload, bobAddress)

sendTokenToPayload := "00a736aa000000000001774667629726ec1fabebcec0d9139bd1c8f72a2301d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e87648170000000000000000000000"
ferdieAddress := "0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c"
decodePayloadAndCompareDestinationAddress(t, sendTokenToPayload, ferdieAddress)

sendNativeTokenPayload := "00a736aa0000000000022121cfe35065c0c33465fbada265f08e9613428a4b9eb4bb717cd7db2abf622e008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48065cd1d00000000000000000000000000e87648170000000000000000000000"
decodePayloadAndCompareDestinationAddress(t, sendNativeTokenPayload, bobAddress)
}

func decodePayloadAndCompareDestinationAddress(t *testing.T, payload, expectedAddress string) {
data := gethCommon.Hex2Bytes(payload)

destination, err := GetDestination(data)
assert.NoError(t, err)

assert.Equal(t, expectedAddress, destination)
}
12 changes: 12 additions & 0 deletions relayer/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ type EthereumConfig struct {
GasLimit uint64 `mapstructure:"gas-limit"`
}

type OFACConfig struct {
Enabled bool `mapstructure:"enabled"`
ApiKey string `mapstructure:"apiKey"`
}

func (p ParachainConfig) Validate() error {
if p.Endpoint == "" {
return errors.New("[endpoint] is not set")
Expand All @@ -41,3 +46,10 @@ func (p PolkadotConfig) Validate() error {
}
return nil
}

func (o OFACConfig) Validate() error {
if o.Enabled && o.ApiKey == "" {
return errors.New("OFAC is enabled but no [apiKey] set")
}
return nil
}
89 changes: 89 additions & 0 deletions relayer/ofac/ofac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package ofac

import (
"encoding/json"
"fmt"
"io"
"net/http"

log "github.com/sirupsen/logrus"
)

type OFAC struct {
enabled bool
apiKey string
}

type Response struct {
Identifications []struct {
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
URL string `json:"url"`
} `json:"identifications"`
}

func New(enabled bool, apiKey string) *OFAC {
return &OFAC{enabled, apiKey}
}

func (o OFAC) IsBanned(source, destination string) (bool, error) {
if !o.enabled {
return false, nil
}

if source != "" {
isSourcedBanned, err := o.isOFACListed(source)
if err != nil {
return true, err
}
if isSourcedBanned {
log.WithField("source", source).Warn("found ofac banned source address")
return true, nil
}
}

if destination != "" {
isDestinationBanned, err := o.isOFACListed(destination)
if err != nil {
return true, err
}
if isDestinationBanned {
log.WithField("destination", destination).Warn("found ofac banned destination address")
return true, nil
}
}

return false, nil
}

func (o OFAC) isOFACListed(address string) (bool, error) {
client := &http.Client{}

req, err := http.NewRequest("GET", fmt.Sprintf("https://public.chainalysis.com/api/v1/address/%s", address), nil)
if err != nil {
return true, err
}

req.Header.Add("Accept", "application/json")
req.Header.Add("X-API-Key", o.apiKey)

resp, err := client.Do(req)
if err != nil {
return true, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return true, err
}

var response Response
err = json.Unmarshal(body, &response)
if err != nil {
return true, err
}

return len(response.Identifications) > 0, nil
}
8 changes: 8 additions & 0 deletions relayer/relays/beacon/header/syncer/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"net/http"
"strconv"
"strings"
"time"

"github.com/ethereum/go-ethereum/common"
log "github.com/sirupsen/logrus"
"github.com/snowfork/snowbridge/relayer/relays/util"
)

Expand Down Expand Up @@ -410,7 +412,13 @@ func (b *BeaconClient) GetBeaconState(stateIdOrSlot string) ([]byte, error) {
}

req.Header.Add("Accept", "application/octet-stream")

startTime := time.Now()
res, err := b.httpClient.Do(req)
endTime := time.Now()
duration := endTime.Sub(startTime)
log.WithFields(log.Fields{"startTime": startTime.Format(time.UnixDate), "endTime": endTime.Format(time.UnixDate), "duration": duration.Seconds()}).Warn("beacon state download time")

if err != nil {
return data, err
}
Expand Down
16 changes: 11 additions & 5 deletions relayer/relays/execution/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
)

type Config struct {
Source SourceConfig `mapstructure:"source"`
Sink SinkConfig `mapstructure:"sink"`
InstantVerification bool `mapstructure:"instantVerification"`
Schedule ScheduleConfig `mapstructure:"schedule"`
Source SourceConfig `mapstructure:"source"`
Sink SinkConfig `mapstructure:"sink"`
InstantVerification bool `mapstructure:"instantVerification"`
Schedule ScheduleConfig `mapstructure:"schedule"`
OFAC config.OFACConfig `mapstructure:"ofac"`
}

type ScheduleConfig struct {
Expand Down Expand Up @@ -46,7 +47,8 @@ type ContractsConfig struct {
}

type SinkConfig struct {
Parachain beaconconf.ParachainConfig `mapstructure:"parachain"`
Parachain beaconconf.ParachainConfig `mapstructure:"parachain"`
SS58Prefix uint8 `mapstructure:"ss58Prefix"`
}

type ChannelID [32]byte
Expand All @@ -70,5 +72,9 @@ func (c Config) Validate() error {
if err != nil {
return fmt.Errorf("schedule config: %w", err)
}
err = c.OFAC.Validate()
if err != nil {
return fmt.Errorf("ofac config: %w", err)
}
return nil
}
Loading
Loading