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

(feat) Add OFAC list check #236

Merged
merged 2 commits into from
Sep 13, 2024
Merged

(feat) Add OFAC list check #236

merged 2 commits into from
Sep 13, 2024

Conversation

PavelInjective
Copy link
Contributor

@PavelInjective PavelInjective commented Sep 8, 2024

Summary by CodeRabbit

  • New Features

    • Introduced an OFAC compliance check within the chain client to prevent processing of blacklisted addresses.
    • Added a new OfacChecker to manage and verify addresses against the OFAC blacklist.
    • Implemented a program to download the OFAC list directly.
  • Bug Fixes

    • Enhanced error handling to return appropriate messages when blacklisted addresses are detected.
  • Tests

    • Added comprehensive tests to validate OFAC list handling and ensure correct functionality.
  • Documentation

    • Updated documentation to include details about the new OFAC compliance features and usage instructions.

Copy link

coderabbitai bot commented Sep 8, 2024

Walkthrough

The changes introduce an OfacChecker to the chainClient, enhancing its functionality to verify addresses against the OFAC blacklist. The NewChainClient function is updated to check if an address is blacklisted upon initialization. Modifications are made to the BuildGenericAuthz and BuildExchangeAuthz methods to include checks for the granter's address. A new test function, TestOfacList, is added to validate the OFAC list handling. Additionally, an example program for downloading the OFAC list is introduced, along with a new JSON file containing blacklisted addresses.

Changes

Files Change Summary
client/chain/chain.go, client/chain/ofac.go Introduced OfacChecker struct and integrated OFAC checks into chainClient methods and initialization logic.
client/chain/chain_test.go Added OfacTestSuite with TestOfacList to validate OFAC list handling and ensure proper error handling for blacklisted addresses.
examples/chain/ofac/1_DownloadOfacList/example.go Created a Go program to download the OFAC list using the chainclient module.
client/metadata/ofac.json Added a JSON file containing blacklisted Ethereum addresses for compliance checks.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChainClient
    participant OfacChecker

    User->>ChainClient: Initialize with address
    ChainClient->>OfacChecker: Check if address is blacklisted
    OfacChecker-->>ChainClient: Return blacklist status
    ChainClient-->>User: Proceed or return error
Loading

🐇 In the code we trust,
With checks that are a must,
OFAC's list we now embrace,
Ensuring compliance in our space.
With each address we verify,
No blacklisted ones slip by!
Hooray for security, hip-hip-hooray! 🎉


Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between a68a1a3 and 0d5726e.

Files selected for processing (5)
  • client/chain/chain.go (5 hunks)
  • client/chain/chain_test.go (3 hunks)
  • client/chain/ofac.go (1 hunks)
  • client/metadata/ofac.json (1 hunks)
  • examples/chain/ofac/1_DownloadOfacList/example.go (1 hunks)
Files skipped from review as they are similar to previous changes (4)
  • client/chain/chain_test.go
  • client/chain/ofac.go
  • client/metadata/ofac.json
  • examples/chain/ofac/1_DownloadOfacList/example.go
Additional comments not posted (1)
client/chain/chain.go (1)

324-324: LGTM!

The changes to initialize the ofacChecker and check if the account's address is blacklisted in the NewChainClient function are approved. This enhances the security by preventing the creation of a chain client for blacklisted addresses.

Also applies to: 445-459


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@PavelInjective PavelInjective force-pushed the ofac-list- branch 3 times, most recently from 28d34ce to 19000bd Compare September 8, 2024 17:34
@@ -32,7 +32,7 @@ func LoadTxFromFile(fileName string) ([]byte, error) {
}

func main() {
network := common.LoadNetwork("devnet", "")
network := common.LoadNetwork("devnet", "lb")
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is not necessary to provide the node name for devnet network. That parameter is only used for testnet and mainnet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

actually, it should be testnet. let me update the PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, this is example file. it should not have been altered at all. force-pushing the correct version of the commit.

Comment on lines 350 to 371
func (cc *chainClient) loadOfacList() error {
response, err := http.Get(defaultOfacListURL)
if err != nil {
return err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return fmt.Errorf("request to the OFAC upstream failed with code: %s", response.Status)
}

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

var ofacList []string
if err := json.Unmarshal(body, &ofacList); err != nil {
return err
}
return nil
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

As per in the Python SDK, I think the idea is to keep a local copy of the ofac.json file. The helper method to download the file will be usefull (and I think required), but it should be in a separate module.
The ChainClient should just load the addresses from the local file

Comment on lines 811 to 802
isAddrInTheList := func(list []string, addr string) bool {
for _, item := range list {
if item == addr {
return true
}
}
return false
}
k, err := txf.Keybase().Key(clientCtx.FromName)
if err != nil {
err = errors.Wrap(err, "error parsing signer account address")
return nil, err
}
signerAddressPubKey, err := k.GetPubKey()
if err != nil {
err = errors.Wrap(err, "error getting signer public key")
return nil, err
}
if isAddrInTheList(c.ofacTxList, sdk.AccAddress(signerAddressPubKey.Address()).String()) {
err = errors.Errorf("Address is in the OFAC list")
return nil, err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we extract this to a method that does the validation and returns an error if the validation fails?

Comment on lines 811 to 818
isAddrInTheList := func(list []string, addr string) bool {
for _, item := range list {
if item == addr {
return true
}
}
return false
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this anonymous function required?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not really. we use it only once. i thought it would be used twice, that's why I created a function.

Comment on lines 61 to 122
func TestOfacList(t *testing.T) {
network := common.LoadNetwork("testnet", "lb")
tmClient, err := rpchttp.New(network.TmEndpoint, "/websocket")
assert.NoError(t, err)

senderAddress, cosmosKeyring, err := accountForTests()
assert.NoError(t, err)

clientCtx, err := NewClientContext(
network.ChainId,
senderAddress.String(),
cosmosKeyring,
)
assert.NoError(t, err)

clientCtx = clientCtx.WithNodeURI(network.TmEndpoint).WithClient(tmClient)

exchangeClient, err := exchangeclient.NewExchangeClient(network)
assert.NoError(t, err)
ctx := context.Background()
req := spotExchangePB.MarketsRequest{
MarketStatus: "active",
}
res, err := exchangeClient.GetSpotMarkets(ctx, &req)
assert.NoError(t, err)

marketsAssistant, err := NewMarketsAssistantInitializedFromChain(ctx, exchangeClient)
assert.NoError(t, err)

cc, err := createClient(senderAddress, cosmosKeyring, network)
assert.NoError(t, err)

defaultSubaccountID := cc.DefaultSubaccount(senderAddress)
marketId := res.Markets[0].MarketId
amount := decimal.NewFromFloat(2)
price := decimal.NewFromFloat(1.02)

order := cc.CreateSpotOrder(
defaultSubaccountID,
&SpotOrderData{
OrderType: exchangetypes.OrderType_BUY, //BUY SELL BUY_PO SELL_PO
Quantity: amount,
Price: price,
FeeRecipient: senderAddress.String(),
MarketId: marketId,
},
marketsAssistant,
)

msg := new(exchangetypes.MsgCreateSpotLimitOrder)
msg.Sender = senderAddress.String()
msg.Order = exchangetypes.SpotOrder(*order)

accNum, accSeq := cc.GetAccNonce()

cc.(*chainClient).ofacTxList = []string{
senderAddress.String(),
}
_, err = cc.BuildSignedTx(clientCtx, accNum, accSeq, 20000, msg)
assert.Error(t, err)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should create a test that does not connect to a any node or indexer if possible. In this case we should not to create an ExchangeClient instance to create a message to test the OFAC restriction

Copy link
Collaborator

@aarmoa aarmoa left a comment

Choose a reason for hiding this comment

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

Great first PR @PavelInjective.
Please lets use dev as the base branch.

Besides the comments I have added, we need to add the validation also in the ChainClient methods that return instances of authztypes.MsgGrant. In that case the address to check is the granter address

@PavelInjective PavelInjective force-pushed the ofac-list- branch 7 times, most recently from 2213553 to 7262c16 Compare September 9, 2024 23:11
@PavelInjective PavelInjective marked this pull request as ready for review September 9, 2024 23:12
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Outside diff range, codebase verification and nitpick comments (1)
client/chain/ofac.go (1)

46-72: LGTM, but consider improving error handling.

The DownloadOfacList function is correctly implemented and handles the download and saving of the OFAC list well. It checks if the download was successful by verifying the HTTP status code, creates the OFAC list file, and writes the downloaded data to it.

However, consider the following improvements:

  • Handle more error cases, such as network errors or file system errors, to make the function more robust.
  • Provide more detailed error messages to help with debugging in case something goes wrong.
Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between b7a68cc and 7262c16.

Files selected for processing (5)
  • client/chain/chain.go (4 hunks)
  • client/chain/chain_test.go (3 hunks)
  • client/chain/ofac.go (1 hunks)
  • examples/chain/ofac/1_DownloadOfacList/example.go (1 hunks)
  • ofac.json (1 hunks)
Additional comments not posted (9)
examples/chain/ofac/1_DownloadOfacList/example.go (1)

1-12: LGTM!

The example program is correctly implemented and serves as a good demonstration of how to download the OFAC list using the chainclient.DownloadOfacList function.

client/chain/ofac.go (3)

23-36: LGTM!

The NewOfacChecker function is correctly implemented and handles the initialization of the OfacChecker well. It checks if the OFAC list file exists, downloads it if it doesn't, and loads the OFAC list into memory.


38-44: LGTM!

The getOfacListPath function is correctly implemented and provides a reliable way to locate the OFAC list file by searching for the "sdk-go" directory and returning the path to the OFAC list file relative to it.


74-85: LGTM!

The loadOfacList method is correctly implemented and handles the loading of the OFAC list into memory well. It reads the OFAC list file and unmarshals the JSON data into the ofacList field of OfacChecker.

ofac.json (1)

1-48: LGTM!

The ofac.json file is a simple JSON array of blacklisted addresses and does not require any changes. It serves as a data source for the OfacChecker.

client/chain/chain_test.go (1)

58-97: LGTM!

The TestOfacList function is a well-structured test that covers the key aspects of OFAC list handling. It sets up the necessary prerequisites, performs the test by attempting to create a new chain client with a blacklisted address, asserts the expected error, and cleans up after the test.

The test function is approved.

client/chain/chain.go (3)

324-324: Looks good!

The addition of the ofacChecker field to the chainClient struct enhances its capability to check if an address is blacklisted according to OFAC regulations.

The struct field addition is approved.


445-460: Great job integrating OFAC list checks!

The modifications to the NewChainClient function enhance the security and compliance aspects of the chainClient by integrating checks against the OFAC list. It prevents the creation of a new chain client if the account's address is blacklisted.

The function modifications are approved.


1200-1202: Consider returning an error instead of panicking.

The modification to the BuildExchangeAuthz function introduces a check if the granter's address is blacklisted, which enhances security by preventing the creation of exchange authorization for blacklisted addresses.

However, similar to the BuildGenericAuthz function, the use of panic for error handling is not ideal. It abruptly terminates the program and does not provide a graceful way to handle the error.

Consider returning an error instead of panicking:

-	if c.ofacChecker.IsBlacklisted(granter) {
-		panic("Address is in the OFAC list")
-	}
+	if c.ofacChecker.IsBlacklisted(granter) {
+		return nil, errors.New("granter address is in the OFAC list")
+	}

This way, the caller of the function can handle the error appropriately.

Comment on lines 87 to 105
func (oc *OfacChecker) IsBlacklisted(address string) bool {
for _, item := range oc.ofacList {
if item == address {
return true
}
}
return false
}
Copy link

@coderabbitai coderabbitai bot Sep 9, 2024

Choose a reason for hiding this comment

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

LGTM, but consider using a more efficient data structure.

The IsBlacklisted method is correctly implemented and provides a simple way to check if an address is blacklisted by iterating over the ofacList and returning true if the address is found in the list.

However, consider using a more efficient data structure, such as a hash set, for faster lookups, especially if the OFAC list grows large.

To use a hash set, modify the ofacList field to be a map[string]bool and update the loadOfacList method to populate the map:

type OfacChecker struct {
    ofacListPath string
    ofacList     map[string]bool
}

func (oc *OfacChecker) loadOfacList() error {
    // ...
    oc.ofacList = make(map[string]bool)
    for _, address := range list {
        oc.ofacList[address] = true
    }
    return nil
}

Then, update the IsBlacklisted method to use the map:

func (oc *OfacChecker) IsBlacklisted(address string) bool {
    return oc.ofacList[address]
}

Copy link
Collaborator

@aarmoa aarmoa Sep 10, 2024

Choose a reason for hiding this comment

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

@PavelInjective we should consider this recommendation from CodeRabbit

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay, let's implement this as a map. however, iterating over a thousand of addresses when sdk instance is created is not a big deal.

client/chain/chain.go Show resolved Hide resolved
Copy link
Collaborator

@aarmoa aarmoa left a comment

Choose a reason for hiding this comment

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

If possible let's move the ofac.json file to client/metadata

Also the OFAC check is still missing in BuildExchangeBatchUpdateOrdersAuthz

Comment on lines 95 to 96
err = DownloadOfacList()
assert.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's have a test here similar to the Python SDK test, that does not require downloading the real file (a test that will not fail if executed in an environment without internet connection)

Comment on lines 87 to 105
func (oc *OfacChecker) IsBlacklisted(address string) bool {
for _, item := range oc.ofacList {
if item == address {
return true
}
}
return false
}
Copy link
Collaborator

@aarmoa aarmoa Sep 10, 2024

Choose a reason for hiding this comment

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

@PavelInjective we should consider this recommendation from CodeRabbit

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 7262c16 and e5a8990.

Files selected for processing (5)
  • client/chain/chain.go (4 hunks)
  • client/chain/chain_test.go (3 hunks)
  • client/chain/ofac.go (1 hunks)
  • client/metadata/ofac.json (1 hunks)
  • examples/chain/ofac/1_DownloadOfacList/example.go (1 hunks)
Files skipped from review due to trivial changes (1)
  • examples/chain/ofac/1_DownloadOfacList/example.go
Files skipped from review as they are similar to previous changes (2)
  • client/chain/chain_test.go
  • client/chain/ofac.go
Additional comments not posted (4)
client/metadata/ofac.json (1)

1-48: LGTM! The file structure and content are valid.

The file is a valid JSON array containing Ethereum addresses, which aligns with the expected OFAC blacklist format based on the file name and PR context.

client/chain/chain.go (3)

324-324: LGTM!

The addition of the ofacChecker field to the chainClient struct is approved.


447-450: LGTM!

The changes to the NewChainClient function are approved:

  • Initializing the ofacChecker is necessary to enable the OFAC list check functionality.
  • Checking if the account's address is blacklisted during client initialization enhances security by preventing the creation of a client for a blacklisted address.

Also applies to: 458-460


1166-1168: Consider returning an error instead of panicking.

The modification to the BuildGenericAuthz function introduces a check if the granter's address is blacklisted, which enhances security by preventing the creation of generic authorization for blacklisted addresses.

However, the use of panic for error handling is not ideal. It abruptly terminates the program and does not provide a graceful way to handle the error.

Consider returning an error instead of panicking:

-	if c.ofacChecker.IsBlacklisted(granter) {
-		panic("Address is in the OFAC list")
-	}
+	if c.ofacChecker.IsBlacklisted(granter) {
+		return nil, errors.New("granter address is in the OFAC list")
+	}

This way, the caller of the function can handle the error appropriately.

Likely invalid or redundant comment.

client/metadata/ofac.json Show resolved Hide resolved
client/chain/chain.go Show resolved Hide resolved
@@ -440,15 +442,23 @@ func NewChainClient(
subaccountToNonce: make(map[ethcommon.Hash]uint32),
}

_ = NewTxFactory(ctx).WithSequence(0).WithAccountNumber(0).WithGas(0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@PavelInjective what is this line for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed it

Copy link
Collaborator

@aarmoa aarmoa left a comment

Choose a reason for hiding this comment

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

Changes look good to me

@PavelInjective PavelInjective merged commit 5f52a16 into dev Sep 13, 2024
4 checks passed
@PavelInjective PavelInjective deleted the ofac-list- branch September 13, 2024 11:36
@coderabbitai coderabbitai bot mentioned this pull request Sep 18, 2024
This was referenced Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants